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

Add API to render sandboxed iframe with inline scripts #132595

Closed
flash1293 opened this issue May 20, 2022 · 11 comments
Closed

Add API to render sandboxed iframe with inline scripts #132595

flash1293 opened this issue May 20, 2022 · 11 comments
Labels
Feature:Security/CSP Platform Security - Content Security Policy NeededFor:VisEditors Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc Team:Security Team focused on: Auth, Users, Roles, Spaces, Audit Logging, and more!

Comments

@flash1293
Copy link
Contributor

Discussed with @lukeelmers and @jportner - FYI @pgayvallet

There should be an API exposed as part of core services to render a sandboxed iframe with a supplied inline script in order to execute this inline script in a safe way as well as allowing the script to render dom elements etc. within the iframe.

To make sure this API isn't abused, any consuming plugin has to register itself in its setup phase. A functional test including a whitelist of all registered plugins will be maintained in order to pull in the security and core teams as a mandatory reviewer of each feature using this service.

Design

  • Add nonce script-src rule to CSP - the nonce has to be a cryptographically safe random string which is regenerated for each request of the Kibana app (kibanaHost/app/<xyz>
  • Pass the generated nonce as part of injected meta data to the client side core services
  • Expose new contract method to plugins: generateSandboxIframeRenderer(pluginId: string): SandboxIframeRenderer
  • The SandboxIframeRenderer#render method is wrapping the supplied script into an inline script tag within the srcDoc of a newly generated iframe element with the nonce passed via injected meta data. The plugin never has access to the nonce itself.
  • The iframe is sandboxed using sandbox="allow-scripts" which disallows everything except for running local scripts.
  • Via html meta tag the CSP can be locked down further: <meta http-equiv="content-security-policy" content="default-src none; script-src 'nonce-${nonce}'; style-src 'nonce-${nonce}'">
  • A consuming plugin gets a reference to the iframe element and can hook up a post message interface to establish communication between the iframe content and the Kibana systems.
  • A functional test can maintain a whitelist of plugins calling generateSandboxIframeRenderer - by configuring the security team as code owner of the functional test, a review of the security team for each plugin using the new API can be enforced
interface SandboxIframeRenderer {
  render: (mountpoint: Element, script: string, bodyHTML: string, options?: { className?: string; style?: object; }) => Element
}

References

See POC here https://github.com/elastic/kibana/pull/131321/files#diff-4825eb8ea1b3f99945f4c0347d8f99081bc698ccfa29428b86f044fa59b7f806R97

@flash1293 flash1293 added Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc Team:Security Team focused on: Auth, Users, Roles, Spaces, Audit Logging, and more! labels May 20, 2022
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-core (Team:Core)

@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-security (Team:Security)

@legrego legrego added NeededFor:VisEditors Feature:Security/CSP Platform Security - Content Security Policy labels May 24, 2022
@pgayvallet
Copy link
Contributor

Thanks for the detailed explanations. Given I couldn't attend to the meeting, I got a few questions:

Expose new contract method to plugins: generateSandboxIframeRenderer(pluginId: string): SandboxIframeRenderer

Did you discuss by any chance which core service it would make the most sense to expose this new API from?

A functional test can maintain a whitelist of plugins calling generateSandboxIframeRenderer

Unless we're expecting all consumers to call the API during their lifecycle methods, I don't think this would work? I would assume that consumers will call generateSandboxIframeRenderer later, when they effectively need to open the iframe?

render: (mountpoint: Element, script: string, bodyHTML: string, options?: { className?: string; style?: object; }) => Element

What script is here exactly? Is that a stringified version of the content of a <script> tag we'll have to inject/wrap with the nonce- directly?

@jportner
Copy link
Contributor

A functional test can maintain a whitelist of plugins calling generateSandboxIframeRenderer

Unless we're expecting all consumers to call the API during their lifecycle methods, I don't think this would work? I would assume that consumers will call generateSandboxIframeRenderer later, when they effectively need to open the iframe?

I think this was a typo --
Our idea was to force consumers to go through some sort of "registration" to access this iframe generation function, and then write the integration test based on which consumers are registered.
Of course that test alone wouldn't give us the actual context of how the consumer is using this iframe, but it would allow the Platform Security team to gauge how this is being used and review the PR where this usage is first introduced.

@flash1293
Copy link
Contributor Author

flash1293 commented May 24, 2022

Did you discuss by any chance which core service it would make the most sense to expose this new API from?

We didn't discuss this, I'm not an expert but in my opinion a new top level service (could be called sandbox or something like that) would be the best fit.

Unless we're expecting all consumers to call the API during their lifecycle methods, I don't think this would work? I would assume that consumers will call generateSandboxIframeRenderer later, when they effectively need to open the iframe?

Apologies, should have explained that better. In addition to Joes explanations, I don't have a strong opinion of how this registration process should look like, open to suggestions. What I had in mind was indeed forcing the user to generate the renderer during the setup phase of their plugin and retaining the instance to use it later on:

class MyPlugin {
  setup(core) {
    this.myIFrameRenderer = core.sandbox.generateSandboxIframeRenderer('myPlugin');
  }
  start(core) {
    core.application.register({
      mount: ({ mountpoint }) => {
           this.myIFrameRenderer.render(mountpoint, theSandboxedScript);
      }
    })
  }
}

This would keep the API relatively simple (no multi step registration plus usage) and allow the core services to drop every reference to the nonce value except for the JS closure which is providing the renderer, minimizing the chances for accidentally leaking it due to refactoring etc.

What script is here exactly? Is that a stringified version of the content of a <script> tag we'll have to inject/wrap with the nonce- directly?

Yes, exactly. Not sure about that part and how it would interact with the bodyHTML idea - due to the nature of visualizations it would probably be alright to drop the bodyHTML argument and just have the custom script (it's always possible to put together the initial body within the script):

Simplified implementation of the iframe renderer:

render(el, myScript) {
 // TODO - maybe use react.renderToString for that?
  const srcDoc = `<html><body><script nonce="${this.nonce}">${myScript}</script></body></html>`;
  ReactDom.render(el, <iframe sandbox="allow-scripts" srcDoc={srcDoc} />);
}

@pgayvallet
Copy link
Contributor

@flash1293 what is the priority regarding this?

@flash1293
Copy link
Contributor Author

@pgayvallet Low priority at the moment, I will update the issue in case we get closer being ready to consume an API like this

@pgayvallet
Copy link
Contributor

@elastic/kibana-security do you know if that's still something we might want, or should I just close this one?

@legrego
Copy link
Member

legrego commented Jul 8, 2024

@pgayvallet I know this was needed for script-based visualizations (#131321), but I don't believe this is on the roadmap, and I'm unsure about its future.

From the security side, we found alternate solutions to hardening our third-party dependencies, which removed our own need for this.

So -- unless we have feedback from the viz team, I think we are ok to close this.

@flash1293
Copy link
Contributor Author

I'm not part of the visualizations team anymore, but it looks like it has been deprioritized: #126748 (comment)

I'll pour one out for this feature, would have loved to see it land at one point :)

@pgayvallet
Copy link
Contributor

Thank you both for your feedback. Closing then

@pgayvallet pgayvallet closed this as not planned Won't fix, can't repro, duplicate, stale Jul 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:Security/CSP Platform Security - Content Security Policy NeededFor:VisEditors Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc Team:Security Team focused on: Auth, Users, Roles, Spaces, Audit Logging, and more!
Projects
None yet
Development

No branches or pull requests

5 participants