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
Conversation
@bostko how is the filter configured? |
61fa6a5
to
f63593c
Compare
7462f7e
to
9e5dff5
Compare
There was a problem hiding this 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"; | |||
|
|||
/** |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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; | ||
|
||
/** |
There was a problem hiding this comment.
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."); |
There was a problem hiding this comment.
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())); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
eb9f5b6
to
0c3fcf2
Compare
There was a problem hiding this 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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this 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, ""); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
*/
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove
There was a problem hiding this comment.
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'.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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(), |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
f592ecb
to
828475b
Compare
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. |
57cccbb
to
6a60db1
Compare
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())); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
6a60db1
to
d32db15
Compare
There was a problem hiding this 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 :)
4fb1f47
to
c5e4afd
Compare
PR is updated. |
c5e4afd
to
e92bfc0
Compare
Include org.apache.cxf.rs.security.cors.CrossOriginResourceSharing implementation
e92bfc0
to
51e568e
Compare
There was a problem hiding this 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
@m4rkmckenna are there comments which I missed to address? |
There was a problem hiding this 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.
Looks good, and other reviewers approved. Will merge now. |
Include org.apache.cxf.rs.security.cors.CrossOriginResourceSharing implementation