Skip to content

Add CORS policy to app spec#45558

Merged
avatus merged 3 commits intomasterfrom
avatus/cors
Sep 9, 2024
Merged

Add CORS policy to app spec#45558
avatus merged 3 commits intomasterfrom
avatus/cors

Conversation

@avatus
Copy link
Copy Markdown
Contributor

@avatus avatus commented Aug 16, 2024

Requires: #45512 (not a hard requirement, but I'd like for it to go in after)
Part of: #17588

The purpose of this PR is to solve the CORS preflight request issue when an app makes a request to another app. The problem currently manifests because preflight requests sent by the browser do not include credentials, therefore Teleport throws the request out and does not forward to the destination app. The three solutions that came up when brainstorming this idea were

  1. Set a CORS policy on the proxy service that could respond to all OPTIONS requests that came in. This would have to be a pretty "open" CORS policy to respond to all preflights for X amount of apps enabled in the app service so it seemed really weak in terms of security and control.
  2. Set a CORS policy in the app spec. This seemed to be the most "teleport-y" way to do things. This would keep unauthenticated requests from going through to the destination app. The downside of this approach is that this CORS policy cannot be dynamic per request, and if the CORS policy is frequently changed in the destination app code itself, this spec policy might get out of sync.
  3. Allow unauthenticated requests through to the destination app to respond to with its own CORS policy. While this way is default how most of the internet works, it seems best to not change the 'no unauthenticated requests' paradigm that the app service currently uses.

This PR implements option number 2. The main benefit here is that, unless a CORS policy is set in the app spec (after this change goes in), then nothing will change.

All of the CORS fields are optional so if a user only wants to set Access-Control-Allow-Headers, that is possible.

Example app spec

app_service:
  enabled: "yes"
  debug_app: true
  apps:
    - name: "api22"
      uri: "http://localhost:4000"
      public_addr: "api.teleport.zarquon.sh"
      cors:
        allowed_origins:
          - "https://client.teleport.zarquon.sh:3080"
        allowed_methods:
          - "POST"
          - "GET"
          - "OPTIONS"
          - "PUT"
          - "DELETE"
        allowed_headers:
          - "Content-Type"
          - "Authorization"
          - "X-Custom-Header"
        allow_credentials: true
        max_age: 86400
    - name: "client"
      uri: "http://localhost:3002"
      public_addr: "client.teleport.zarquon.sh"
      required_apps:
        - "api22"

I've uploaded my repo that I used during testing to work through the required apps PR as well as this CORS pr. You can use it here if you'd like! https://github.com/avatus/corstest

TODO: add docs when we agree on the contents of the spec

changelog: You can now specify a CORS policy per application in the application config.

@github-actions
Copy link
Copy Markdown
Contributor

The PR changelog entry failed validation: Changelog entry not found in the PR body. Please add a "no-changelog" label to the PR, or changelog lines starting with changelog: followed by the changelog entries for the PR.

// CORS defines the CORS policy for AppSpecV3
message CORS {
// AllowedOrigins specifies which origins are allowed to access the app.
repeated string AllowedOrigins = 1 [(gogoproto.jsontag) = "allowed_origins,omitempty"];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we use the new proto style rules with names using snake case instead of camel?

Copy link
Copy Markdown
Contributor Author

@avatus avatus Aug 19, 2024

Choose a reason for hiding this comment

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

You mean like this? I'll make any recommended change to the naming conventions, just want to make sure I know what you mean

repeated string allowed_origins = 1 [(gogoproto.jsontag) = "allowed_origins,omitempty"];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes.

}

// CORS defines the CORS policy for AppSpecV3
message CORS {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we name it CORSSpec?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes!

Comment thread lib/service/service.go

// makeApplicationCORS converts a servicecfg.CORS to a types.CORS.
func makeApplicationCORS(c *servicecfg.CORS) *types.CORS {
if c == nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does Teleport infers any default rules as of today or it forwards the CORS requests directly into the application?

Copy link
Copy Markdown
Contributor Author

@avatus avatus Aug 19, 2024

Choose a reason for hiding this comment

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

Currently any preflight requests are completely thrown out because they are unauthenticated. So no defaults. (technically its not "thrown out", its just redirected to the /web/launch route, but preflights cannot contain a redirect and anything other than a 2XX response counts as a fail anyway)

Comment thread lib/web/apiserver.go
foundApp := servers[0].GetApp()
corsPolicy := foundApp.GetCORS()
if corsPolicy == nil {
return
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Given that we intercept every single cors request, won't this break existing apps without cors specified?
Should we mark it as breaking change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The wouldn't be a breaking change.

  1. Every prelight request is already "broken" so nothing would change
  2. its opt in, so only users who wanted CORS to work would have to explicitly set it.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Sep 4, 2024

🤖 Vercel preview here: https://docs-7bjd0wxeg-goteleport.vercel.app/docs/ver/preview

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Sep 4, 2024

🤖 Vercel preview here: https://docs-4t2o3ogrr-goteleport.vercel.app/docs/ver/preview

@avatus avatus requested a review from rosstimothy September 5, 2024 15:05
Comment on lines +948 to +949
// CORSSpec defines the CORS policy for AppSpecV3
message CORSSpec {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestoin: s/Spec/Policy to more accurately represent this type?

Suggested change
// CORSSpec defines the CORS policy for AppSpecV3
message CORSSpec {
// CORSPolicy defines the CORS policy for AppSpecV3
message CORSPolicy {

Comment thread lib/config/fileconf.go
Comment thread lib/service/servicecfg/app.go
Comment thread lib/web/apiserver.go Outdated

servers, err := app.Match(r.Context(), h.handler.cfg.AccessPoint, app.MatchPublicAddr(publicAddr))
if err != nil {
h.handler.log.Error("failed to match application with public addr %s", publicAddr)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this truly an error? Or is this more information? In other words might it be expected that we don't find a match for all preflight requests?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I suppose it wouldn't be an error error because the app itself would still function (granted, CORS wont work, but thats the same as now). I'll switch to info

Comment thread lib/web/apiserver.go Outdated
}

if len(servers) == 0 {
h.handler.log.Error("failed to match application with public addr %s", publicAddr)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same question as above.

Comment thread lib/web/apiserver.go
Comment on lines +362 to +364
foundApp := servers[0].GetApp()
corsPolicy := foundApp.GetCORS()
if corsPolicy == nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there any chance that we might have multiple matching apps, each with a slightly different CORS policy?

Comment thread lib/service/service.go Outdated
return nil
}

return &types.CORSSpec{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we need to sanitize any of this data? Does it matter if it contains a bunch of empty strings? Or a really long string? Is there any possibility that these values could be defined in such a way that it would prevent the preflight request from completing successfully?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

the max length of a custom header is dependent on web server but not defined in the web spec. Apache is 8kb for a single header length.

extra spaces or trailing spaces in the origins would cause the preflight to fail. should that be left to correct configuration or do we normally remove stuff like leading/trailing spaces elsewhere? perhaps note it in the docs? (which are coming after all this is approved). happy either way

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Sep 5, 2024

🤖 Vercel preview here: https://docs-jrpum396p-goteleport.vercel.app/docs/ver/preview

@avatus avatus requested a review from rosstimothy September 6, 2024 20:45
@avatus avatus force-pushed the avatus/redirect branch 3 times, most recently from 6dc1fcb to 4698e76 Compare September 6, 2024 23:09
Base automatically changed from avatus/redirect to master September 7, 2024 01:11
@avatus
Copy link
Copy Markdown
Contributor Author

avatus commented Sep 7, 2024

rebased onto master and cleaned up some of the make derive and make grpc type of commits

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Sep 7, 2024

🤖 Vercel preview here: https://docs-45jy80nki-goteleport.vercel.app/docs/ver/preview

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Sep 7, 2024

🤖 Vercel preview here: https://docs-o4echbhrf-goteleport.vercel.app/docs/ver/preview

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Sep 7, 2024

🤖 Vercel preview here: https://docs-6zpweudgt-goteleport.vercel.app/docs/ver/preview

@avatus avatus added this pull request to the merge queue Sep 9, 2024
Merged via the queue into master with commit 8d8df0e Sep 9, 2024
@avatus avatus deleted the avatus/cors branch September 9, 2024 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants