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 experimental CORS server support #519

Merged
merged 3 commits into from Jan 23, 2017
Merged

Conversation

bostko
Copy link
Contributor

@bostko bostko commented Jan 13, 2017

Include org.apache.cxf.rs.security.cors.CrossOriginResourceSharing implementation

@m4rkmckenna
Copy link
Member

@bostko how is the filter configured?

@bostko bostko force-pushed the cxf_cors_filter branch 7 times, most recently from 61fa6a5 to f63593c Compare January 17, 2017 15:52
@bostko bostko changed the title WIP Add CXF CrossOriginResourceSharing filter Add CXF CrossOriginResourceSharing filter Jan 17, 2017
@bostko bostko force-pushed the cxf_cors_filter branch 2 times, most recently from 7462f7e to 9e5dff5 Compare January 17, 2017 16:33
@bostko bostko changed the title Add CXF CrossOriginResourceSharing filter Add experimental CORS server support Jan 17, 2017
Copy link
Contributor

@geomacy geomacy left a comment

Choose a reason for hiding this comment

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

Some initial comments; haven't finished looking through it but will continue tomorrow.

@@ -61,6 +61,37 @@
/** whether feeds are automatically registered when set on entities, so that they are persisted */
public static final String FEATURE_FEED_REGISTRATION_PROPERTY = FEATURE_PROPERTY_PREFIX+".feedRegistration";

/**
Copy link
Contributor

Choose a reason for hiding this comment

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

I didn't say that CORS was always insecure; used judiciously, it can be a perfectly acceptable mechanism for many use-cases. Used badly or inappropriately, it can open you up to increased security risks, but the same can be said about lots of things. I would remove the warnings from this comment, and simply explain what the feature is that is being enabled. It is up to users to determine whether CORS is applicable in their case and to enable it if so.

* In short, the proposed architecture and use of CORS is more complex, less secure,
* and more difficult to manage than the alternative of web client ---> Apache Brooklyn Server ----> fan out to backend servers + Apache Brooklyn API + etc.
*
* Notes by Geoff Macartney.
Copy link
Contributor

Choose a reason for hiding this comment

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

Definitely don't blame me! :-)

import java.util.Collections;
import java.util.List;

/**
Copy link
Contributor

Choose a reason for hiding this comment

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

Why repeat this? In any case, as above, just get rid of these warnings.

private static final boolean brooklynFeatureEnabled = BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_CORS_CXF_PROPERTY);
static {
if (brooklynFeatureEnabled) {
LOGGER.warn("CORS brooklyn feature enabled.");
Copy link
Contributor

Choose a reason for hiding this comment

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

info not warn

public CorsImplSupplierFilter(@Nullable ManagementContext managementContext) {
setFindResourceMethod(false);
if (managementContext != null) {
setAllowOrigins(JavaGroovyEquivalents.<List<String>>elvis(managementContext.getConfig().getConfig(ALLOWED_ORIGINS), Collections.<String>emptyList()));
Copy link
Contributor

Choose a reason for hiding this comment

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

why use elvis here? Also prefer ImmutableList.of() to emptyList.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is possible for ALLOWED_ORIGINS to be null or empty, if so then I find elvis use full to setting it to an empty list.
Collections.emptyList() is used in CrossOriginResourceSharingFilter default constructor so I use the same class which is also immutable.

*/
@Provider
public class CorsImplSupplierFilter extends CrossOriginResourceSharingFilter {
public static final ConfigKey<List<String>> ALLOWED_ORIGINS = ConfigKeys.newConfigKey(new TypeToken<List<String>>() {}, BrooklynFeatureEnablement.FEATURE_CORS_CXF_PROPERTY + ".allowedOrigins");
Copy link
Contributor

Choose a reason for hiding this comment

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

We should use Karaf's Configuration Admin service to do the configuration in the Karaf case.

See for example how this is done in the karaf blueprint and used to inject configuration into the launcher.

Copy link
Member

Choose a reason for hiding this comment

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

@geomacy I see osgilauncher.cfg as configuration done by the distribution creator and brooklyn.cfg configured by the user. That's why I think it makes sense to have this as configurable in brooklyn.cfg since the origin is something that the user will decide on.

Copy link
Member

Choose a reason for hiding this comment

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

@neykov Disagree the cors configuration should be in a separate PID

As the CORS configuration has the potential to be hundreds of lines long

Copy link
Member

Choose a reason for hiding this comment

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

Personal preference to stick to the standard configuration approach to Brooklyn and not split it in smaller files.
Hundreds of lines long sounds like something has gone horribly wrong. I'd worry more about sending it on the network.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think the config will be hundreds of lines as it is only the domain of the requesting site that needs to be added to the header, not separate entries for each page that may do the request. There will only be a handful of domains in most use-cases, I expect.

@neykov for the configuration, I agree up to a point; we probably need to think a bit about recommendations for what sort of configs are most appropriate in brooklyn.cfg, and what are better done in the conventional Karaf way of having a PID for a feature, e.g. for CORS you would have a cfg file such as org.apache.brooklyn.rest.filter.cors.cfg with the appropriate config in it. However, as there is only one property to be configured, I think that is probably overkill for this scenario.

@bostko bostko force-pushed the cxf_cors_filter branch 3 times, most recently from eb9f5b6 to 0c3fcf2 Compare January 18, 2017 09:15
Copy link
Member

@neykov neykov left a comment

Choose a reason for hiding this comment

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

Include the filter in /brooklyn-rest-server/src/main/webapp/WEB-INF/web.xml as well. LGTM otherwise.


public CorsImplSupplierFilter(@Nullable ManagementContext managementContext) {
setFindResourceMethod(false);
if (managementContext != null) {
Copy link
Member

Choose a reason for hiding this comment

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

Why would this be null in normal operation. Better throw instead of silently ignoring the problem.

Copy link
Member

@tbouron tbouron left a comment

Choose a reason for hiding this comment

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

Minor changes to apply to the unit tests + comments from others.

Otherwise, LGTM


response = HttpTool.execAndConsume(client, httpOptionsRequest("script/groovy", shouldAllowOrigin, "POST"));
assertNull(response.getHeaderLists().get(HEADER_AC_ALLOW_ORIGIN), "Access Control Header should not be available.");
assertOkayResponse(response, "");
Copy link
Member

Choose a reason for hiding this comment

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

You don't actually test that the request is rejected if it comes from another domain

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it's meant to be rejected, if I understand it right, it is the browser that will discard the response as it won't have the header it needs to bypass the same origin policy?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@geomacy that's right.

public class CorsFilterLauncherTest extends BrooklynRestApiLauncherTestFixture {
@Test
public void testEnabledCorsSendsBasicAllowResponse() throws IOException {
BrooklynFeatureEnablement.enable(BrooklynFeatureEnablement.FEATURE_CORS_CXF_PROPERTY);
Copy link
Member

@tbouron tbouron Jan 18, 2017

Choose a reason for hiding this comment

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

I would break down all tests into single, isolated unit test. Will be simpler to see and check what has been tested or not

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You will save only BrooklynRestApiLauncher apiLauncher instantiation.
I do not think making a big test method will be simpler to read.
However I welcome suggestions for shortening assertions.

Copy link
Member

Choose a reason for hiding this comment

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

I meant, the current test function contains 4 separate tests. I would break those 4 down into their own test function to be more readable.

@Test
public void testCorsIsDisabled() throws IOException {
BrooklynFeatureEnablement.disable(BrooklynFeatureEnablement.FEATURE_CORS_CXF_PROPERTY);
final String shouldAllowOrigin = "http://foo.bar.com";
Copy link
Member

Choose a reason for hiding this comment

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

Same as above

@@ -61,6 +61,36 @@
/** whether feeds are automatically registered when set on entities, so that they are persisted */
public static final String FEATURE_FEED_REGISTRATION_PROPERTY = FEATURE_PROPERTY_PREFIX+".feedRegistration";

/**
* <p>
* Enabling CORS is strongly discouraged.
Copy link
Contributor

Choose a reason for hiding this comment

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

Valentin, as I mentioned before, I think you should remove this comment. It's not possible for us to know all the scenarios users may have, some of them may simply require CORS, and if they use it judiciously then it can be fine.

I would replace the entire comment with simply

/**
Enables support for Cross Origin Resource Sharing (CORS) filtering on requests in BrooklynWebServer.
If enabled, the allowed origins for the CORS headers should be configured using the 
"brooklyn.experimental.feature.corsCxfFeature. allowedOrigins" property.
If this is not is not supplied then allowedOrigins will be a wildcard on all domains; this is strongly discouraged.
Currently there is no support for varying these headers on a per-API-resource basis, that is, the same configured headers are applied to all requests.
*/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it is valuable to have more information.
What others think?

Copy link
Member

Choose a reason for hiding this comment

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

Remove

Copy link
Contributor

Choose a reason for hiding this comment

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

The point is that you can't tell at this level whether CORS is appropriate or not; that's more an architectural question. Sometimes CORS is a proper solution to a given need to integrate multiple server backends onto a page in a web application (e.g. with a plugin based web application). Sometimes it is a hack to glue in some REST call that you should really be handling server side. We can't simply say 'use of CORS is discouraged'.

Copy link
Member

Choose a reason for hiding this comment

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

CORS per se is a good thing. When combined with some other features like basic auth could be a terrible idea because the credentials need to be shared with all clients. Much easier for malicious users to extract the credentials from the javascript env and use them any way they like.
Basic auth would be the reason why I'd stick to proxy until we get something smarter like web tokens.

I like @geomacy's suggestion above, but perhaps with the proxy recommendation added to it.

import java.util.List;

/**
* Using CORS is strongly discouraged.
Copy link
Contributor

Choose a reason for hiding this comment

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

as above, just delete this line and leave the one referring them to the other comment

@@ -72,7 +72,7 @@ public Response toResponse(Throwable throwable1) {
Exceptions.collapse(throwable1));
} else {
LOG.debug("REST request running as {} threw: {}", Entitlements.getEntitlementContext(),
Copy link
Contributor

Choose a reason for hiding this comment

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

This will lose the stack trace information; I suggest replacing this one line with one at info and one at debug, something like

LOG.info("CorsImplSupplierFilter running in {} caught: {}", Entitlements.getEntitlementContext(), throwable1.getMessage());
LOG.debug("CorsImplSupplierFilter exception details", throwable1);

similarly in other places where the same thing is being done

import static org.testng.Assert.assertNull;

/**
* If brooklyn.experimental.feature.corsCxfFeature.allowedOrigins is not supplied then allowedOrigins will be all domains.
Copy link
Contributor

@geomacy geomacy Jan 18, 2017

Choose a reason for hiding this comment

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

Would it be worth adding a test for that? Just a suggestion, I don't think it's indispensable.

@bostko bostko force-pushed the cxf_cors_filter branch 2 times, most recently from f592ecb to 828475b Compare January 18, 2017 10:47
@geomacy
Copy link
Contributor

geomacy commented Jan 18, 2017

I think that's all the comments I want to make except that it would be good to have an actual web page test case that demonstrates it working; maybe @m4rkmckenna could help out with that?

Apart from the particular comments above I'm happy to say this looks good to me.

@bostko bostko force-pushed the cxf_cors_filter branch 6 times, most recently from 57cccbb to 6a60db1 Compare January 18, 2017 15:36
setFindResourceMethod(false);
Preconditions.checkNotNull(mgmt,"ManagementContext should be suppplied to CORS filter.");
setAllowOrigins(JavaGroovyEquivalents.<List<String>>elvis(
mgmt.getConfig().getConfig(ALLOWED_ORIGINS), Collections.<String>emptyList()));
Copy link
Member

Choose a reason for hiding this comment

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

@bostko Please expose all configuration the filter provides as it is all needed

See https://cxf.apache.org/javadoc/latest/org/apache/cxf/rs/security/cors/CrossOriginResourceSharingFilter.html

Copy link
Member

Choose a reason for hiding this comment

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

If we start supporting all config options of the filter, then I could see this being its own PID.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@neykov could you give guidance how this can be done?

Copy link
Member

@m4rkmckenna m4rkmckenna left a comment

Choose a reason for hiding this comment

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

Im happy once other tests have been addressed

Ive pen tested to the best of my ability :)

@bostko bostko force-pushed the cxf_cors_filter branch 3 times, most recently from 4fb1f47 to c5e4afd Compare January 20, 2017 22:35
@bostko
Copy link
Contributor Author

bostko commented Jan 20, 2017

PR is updated.
To test it in the karaf distribution you need to have etc/org.apache.brooklyn.rest.filter.cors.cfg

bostko and others added 3 commits January 22, 2017 06:45
Include org.apache.cxf.rs.security.cors.CrossOriginResourceSharing implementation
Copy link
Member

@m4rkmckenna m4rkmckenna left a comment

Choose a reason for hiding this comment

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

Tested 51e568e with apache/brooklyn-dist#81

Once other comments addressed good to merge

@bostko
Copy link
Contributor Author

bostko commented Jan 23, 2017

@m4rkmckenna are there comments which I missed to address?

@m4rkmckenna
Copy link
Member

@bostko One from @geomacy RE javadoc (DRY) ... I think the rest have been addressed

Copy link
Contributor

@geomacy geomacy left a comment

Choose a reason for hiding this comment

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

My comments have been addressed.

@geomacy
Copy link
Contributor

geomacy commented Jan 23, 2017

Looks good, and other reviewers approved. Will merge now.

@asfgit asfgit merged commit 51e568e into apache:master Jan 23, 2017
asfgit pushed a commit that referenced this pull request Jan 23, 2017
Add experimental CORS server support

Include org.apache.cxf.rs.security.cors.CrossOriginResourceSharing implementation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants