log request for "CORS policy execution failed" - asp.net-core

My ASP.NET Core 3.0 in a particular configuration/deployment logs:
[INF] CORS policy execution failed.
[INF] Request origin https://bla.com does not have permission to access the resource.
How can I log at that point the resource that was requested for debugging ?
(note this question is not about the actual issue or solving it)
(note that I am not after globally increasing the log level etc)

Well, that middleware is locked down pretty badly, and I haven't found any sensible way to hook into it.
If you want to replace the CorsMiddleware, you can't just create a decorator that calls Invoke() on the middleware, because you'll have no idea what happened.
Another solution might be to replace the CorsService:ICorsService registration in the service collection with a decorator, and then check the CorsResult after delegating the call to EvaluatePolicy(). That way you could emit an additional log message close to where the original message is emitted.
But there is another possible solution, both very simple and very crude: To check what happened in the request. Albeit that is a bit farther away from the original logged message.
The code below is a delegate added to the pipeline (in Startup/Configure, before .UseCors()) that checks if the request was a preflight request (the same way CorsService does), and if it was successful, i.e. the AccessControlAllowOrigin header is present. If it wasn't successful, it logs a message with the same EventId and source as the CorsService.
app.Use(async (ctx, next) =>
{
await next();
var wasPreflightRequest = HttpMethods.IsOptions(ctx.Request.Method)
&& ctx.Request.Headers.ContainsKey(CorsConstants.AccessControlRequestMethod);
var isCorsHeaderReturned = ctx.Response.Headers.ContainsKey(HeaderNames.AccessControlAllowOrigin);
if (wasPreflightRequest && !isCorsHeaderReturned)
{
ctx.RequestServices.GetRequiredService<ILoggerFactory>()
.CreateLogger<CorsService>()
.LogInformation(new EventId(5, "PolicyFailure"),
$"CORS preflight failed at resource: {ctx.Request.Path}.");
}
});
Based on my testing it seems to work. ¯\_(ツ)_/¯
It might not be what you were looking for, but who knows, maybe it will be useful for someone.
(Obviously a good way to deal with these things is to use a structured logging solution, like Serilog, and add enrichers to capture additional request information, or add stuff manually to a diagnostic context. But setting that up is quite a bit more involved.)

Related

Serilog using EnrichDiagnosticContext with additional properties not being logged in SignalR Hub

I have recently implemented Serilog logging into my ASP.NET Core/.NET5 web app that uses SignalR. I'm using the Elasticsearch sink and everything is largely working as expected. I decided to add some additional HttpContext properties to be logged on each request, so I went down the road of extending the call to UseSerilogRequestLogging() in StartUp.cs as to enrich the diagnostic context with a couple of extra properties (mainly because this seemed like the simplest way to do it):
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("HttpRequestClientIP", httpContext.Connection.RemoteIpAddress);
diagnosticContext.Set("UserName", httpContext.User?.Identity?.Name == null ? "(anonymous)" : httpContext.User.Identity.Name);
};
});
At first, this seemed to work as expected until I noticed it wasn't always working. I really want the extra properties logged on all log records written, and it seems to work fine on log records that are written automatically by Serilog when typical HTTP GETs, HTTP POSTs, etc. occur... But in my Signalr Hub class, I have a couple of places where I'm manually writing my own log records like Logger.Log(LogLevel.Information, "whatever.."), but these extra properties are simply not there on these records.
What am I missing here? Is it something about this being in a Signalr Hub that makes them unavailable? Or perhaps there's something I'm doing wrong with my Logger.Log() calls?
Any ideas would be appreciated.
Thanks-
It's not gonna to work with signalR.
Behind the screen, app.UseSerilogRequestLogging make use of a middleware in the request pipeline, that call RequestLoggingMiddleware as what you can see in detail here.
SignalR use the first Http request to setting to connection up to websocket, which won't goes through the pipeline at all. Therefore, doesn't have anything to do with RequestLoggingMiddleware, which you are using to logging out the request.
I finally ended up going with a couple of custom Enrichers. I did experiment briefly with middleware vs enrichers and they both seem to work as expected. Both always added the additional properties to all log entries. I'm still not quite sure I understand why the DiagnosticContext option behaves the way it does, unless it is simply due to the logging in question being in a SignalR hub as #Gordon Khanh Ng. posted. If that were the root of the problem though, you wouldn't think the enrichers or middleware would work either.

HttpContext.Session in Blazor Server Application

I am trying to use HttpContext.Session in my ASP.NET Core Blazor Server application (as described in this MS Doc, I mean: all correctly set up in startup)
Here is the code part when I try to set a value:
var session = _contextAccessor.HttpContext?.Session;
if (session != null && session.IsAvailable)
{
session.Set(key, data);
await session.CommitAsync();
}
When this code called in Razor component's OnAfterRenderAsync the session.Set throws following exception:
The session cannot be established after the response has started.
I (probably) understand the message, but this renders the Session infrastructure pretty unusable: the application needs to access its state in every phase of the execution...
Question
Should I forget completely the DistributedSession infrastructure, and go for Cookies, or Browser SessionStorage? ...or is there a workaround here still utilizing HttpContext.Session? I would not want to just drop the distributed session infra for a way lower level implementation...
(just for the record: Browser's Session Storage is NOT across tabs, which is a pain)
Blazor is fundamentally incompatible with the concept of traditional server-side sessions, especially in the client-side or WebAssembly hosting model where there is no server-side to begin with. Even in the "server-side" hosting model, though, communication with the server is over websockets. There's only one initial request. Server-side sessions require a cookie which must be sent to the client when the session is established, which means the only point you could do that is on the first load. Afterwards, there's no further requests, and thus no opportunity to establish a session.
The docs give guidance on how to maintain state in a Blazor app. For the closest thing to traditional server-side sessions, you're looking at using the browser's sessionStorage.
Note: I know this answer is a little old, but I use sessions with WebSockets just fine, and I wanted to share my findings.
Answer
I think this Session.Set() error that you're describing is a bug, since Session.Get() works just fine even after the response has started, but Session.Set() doesn't. Regardless, the workaround (or "hack" if you will) includes making a throwaway call to Session.Set() to "prime" the session for future writing. Just find a line of code in your application where you KNOW the response hasn't sent, and insert a throwaway call to Session.Set() there. Then you will be able to make subsequent calls to Session.Set() with no error, including ones after the response has started, inside your OnInitializedAsync() method. You can check if the response is started by checking the property HttpContext.Response.HasStarted.
Try adding this app.Use() snippet into your Startup.cs Configure() method. Try to ensure the line is placed somewhere before app.UseRouting():
...
...
app.UseHttpsRedirection();
app.UseStaticFiles();
//begin Set() hack
app.Use(async delegate (HttpContext Context, Func<Task> Next)
{
//this throwaway session variable will "prime" the Set() method
//to allow it to be called after the response has started
var TempKey = Guid.NewGuid().ToString(); //create a random key
Context.Session.Set(TempKey, Array.Empty<byte>()); //set the throwaway session variable
Context.Session.Remove(TempKey); //remove the throwaway session variable
await Next(); //continue on with the request
});
//end Set() hack
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
...
...
Background Info
The info I can share here is not Blazor specific, but will help you pinpoint what's happening in your setup, as I've come across the same error myself. The error occurs when BOTH of the following criteria are met simultaneously:
Criteria 1. A request is sent to the server with no session cookie, or the included session cookie is invalid/expired.
Criteria 2. The request in Criteria 1 makes a call to Session.Set() after the response has started. In other words, if the property HttpContext.Response.HasStarted is true, and Session.Set() is called, the exception will be thrown.
Important: If Criteria 1 is not met, then calling Session.Set() after the response has started will NOT cause the error.
That is why the error only seems to happen upon first load of a page--it's because often in first loads, there is no session cookie that the server can use (or the one that was provided is invalid or too old), and the server has to spin up a new session data store (I don't know why it has to spin up a new one for Set(), that's why I say I think this is a bug). If the server has to spin up a new session data store, it does so upon the first call to Session.Set(), and new session data stores cannot be spun up after the response has started. On the other hand, if the session cookie provided was a valid one, then no new data store needs to be spun up, and thus you can call Session.Set() anytime you want, including after the response has started.
What you need to do, is make a preliminary call to Session.Set() before the response gets started, so that the session data store gets spun up, and then your call to Session.Set() won't cause the error.
SessionStorege has more space than cookies.
Syncing (two ways!) the sessionStorage is impossible correctly
I think you are thinking that if it is on the browser, how can you access that in C#? Please see some examples. It actually read from the browser and transfers (use) on the server side.
sessionstorage and localstorage in blazor are encrypted. We do not need to do extra for encryption. The same applies for serialization.

Apache Camel Choice(): Do Nothing

I have made an application using Apache Camel that integrates well with AWS S3. Here is the code:
Predicate newFilePred = header(NEW_FILE_RECEIVED_HEADER).isEqualTo(SUCCESS);
from(incomingEndpoint)
.process((exchange) -> {
logger.info("Checking S3 bucket.");
Date newFileUploadDate = (Date) exchange.getIn().getHeaders().get("CamelAwsS3LastModified");
if (fileIsNew(newFileUploadDate))
exchange.getIn().setHeader(NEW_FILE_RECEIVED_HEADER, SUCCESS);
} else {
exchange.getIn().setHeader(NEW_FILE_RECEIVED_HEADER, FAILURE);
}
})
.choice()
.when(newFilePred)
.to(outgoingEndpoint)
.endChoice()
.end();
The application works well, but it throws a really annoying warning seen below:
Camel Thread #1 - aws-s3://mybucket] | c.a.s.s.i.S3AbortableInputStream Not all bytes were read from the
S3ObjectInputStream, aborting HTTP connection. This is likely an error and may result in sub-optimal behavior.
Request only the bytes you need via a ranged GET or drain the input stream after use. request_id=
I know the problem is that I dont direct the output of the file anywhere if the header "NEW_FILE_RECEIVED_HEADER" is set to failure. This is by design because I do not want to download the file in this case. Is there anyway to tell Camel to abort the connection properly after I have identified the file as "unwanted"? I could create another camel route directly to a "trash" directory, but these would be useless cycles.
Thank you for your help!
For lack of a better solution, I managed to mute these messages by implementing a garbage/failure route that finishes the http get/input stream from S3.
If someone comes up with a better method or a different approach entirely, please feel free to "beat" this answer. The underlying rule that was discovered is that a finished route (A -> B) is required or a warning will be thrown. The warnings are harmless unless you view a polluted log file like I do.
Finished route conditionals look like this:
.choice()
.when(newFilePred)
.to(successfulOutgoingEndpoint)
.otherwise()
.to(failureOutgoingEndpoint);

How to access error response body with AngularJS

my head is spinning cause of the following issue. I'm accessing my webservice (running on my localhost:4434) with AngularJS and if something goes wrong, the webservice sends a response 400 containing a json body which contains a message that tells you what exactly went wrong.
Problem is I cannot access the message on the client? It is almost as if it never reaches the client?? (This isn't the case, I've confirmed that it reaches the client already) This is the angular code that I use on the client site.
$scope.create = function() {
$http.post('http://localhost:4434/scrapetastic/foo', $scope.bar).
success(function(data, status, headers, config) {
console.log("Call to log: "+status);
console.log("Call to log: "+data);
}).
error(function(data, status) {
console.log("Error|Data:"+data);
console.log(status);
});
}
If I submit malformed data a corresponding error response is generated but as I said ... somehow I cannot access the message that is contained in the response body. This is what I get:
I've tried all sorts of things but am seriously stuck now...perhaps someone has an idea on how to access the payload of the response or at least what to do next? I'm also dealing with CORS perhaps it has something to do with that.
Thanks!
I'm going to take a wild guess here and say that your problem is an XSS issue.
Not only do you not have the data variable, but as far as I can tell from your screenshot, status == 0.
Your screenshot also says Origin: http://localhost, which makes this request considered XSS (since the port is different). That would explain why status is 0.
Edit: You can use jsonp to get around the issue.

Correct way to skip authorization with ImageResizer

HttpConext object has SkipAutorization property that is used to disable the authorization check in UrlAuthorizationModule, which is a part of standard asp.net pipeline.
ImageResizer calls UrlAuthorizationModule.CheckUrlAccessForPrincipal directly, outside of the normal asp.net pipeline. As a result the SkipAutorization property is not honoured.
A workaround to that would be:
protected void Application_Start(object sender, EventArgs e)
{
// Ask ImageResizer not to re-check authorization if it's skipped
// by means of the context flag
Config.Current.Pipeline.OnFirstRequest +=
(m, c) =>
{
Config.Current.Pipeline.AuthorizeImage +=
(module, context, args) =>
{
if (context.SkipAuthorization)
{
args.AllowAccess = true;
}
};
};
}
The outer OnFirstRequest here is to make sure that the AuthorizeImage subscription is happening after all plugins has been loaded so it's last in chain to execute.
I don't like this workaround because it's quite implementation dependent. For example if ImageResizer plugins loading is moved from onFirstRequest to elsewhere it will break.
It would be nice if this is fixed in ImageResizer itself. I would suggest changing the additional Autorization check in InterceptModule to something along these lines:
//Run the rewritten path past the auth system again, using the result as the default "AllowAccess" value
bool isAllowed = true;
if (canCheckUrl) try {
isAllowed = conf.HonourSkipAutorization && app.Context.SkipAuthorization
|| UrlAuthorizationModule.CheckUrlAccessForPrincipal(virtualPath, user, "GET");
} catch (NotImplementedException) { } //For MONO support
Would that be appropriate, or is there a better solution?
In the last part of the question, I'll describe my use case, reading is entirely optional, but it gives perspective how this query came to be.
In an asp.net application I have an HttpHandler that serves pdf documents. It accepts document id and security information in url and headers (I'm using OAuth) and it performs all the security checks and if they succeed the pdf document path is retrieved from the database, and the file is served to the client by Response.WriteFile.
I need to provide preview of a pdf page as an image, and I'm using ImageResize with the PdfRenderer plugin for that.
Unfortunately the path of the pdf is not know until my file handler have worked, and this is too late for ImageResizer to act on the request since all the magic happens in PostAuthorizeRequest which is (obviously) before a handler runs.
To work around this I re-wrote my HttpHandler as HttpModule, where it's executed on BeginRequest. If the authorization checks fail, the request is severed right there. If they are ok, then I use PathRewrite to point to the resulting pdf and at the same time write the proper Content-Type and other headers to the response. At the same time I set context.SkipAutorization flag, because, since the pdf files can't be accessible via a direct url as per web.config configuration, the pipeline would not even get to the PostAuthorizeRequest if authorization is not skipped. It is safe to skip authorization in this case, since all required check has already been performed by the module.
So this allows the execution flow to get to ImageResizer. But then Image resizer decides that it wants to re-check the authorization on the pdf url. Which fails unless you apply the workaround above.
What is the rationale for this re-check? In the scenario above, when ImageResizer has work to do, the image that it is to serve is not what appears in the URL and the auth check has been already done by the asp.net pipeline, now when we are in PostAuthorizeRequest. In which cases is this re-check useful?
Update: The latest version of ImageResizer respects the HttpContext.SkipAuthorization boolean, making the event handler no longer necessary.
Your work-around is exactly the right way to deal with this, and is forwards-comaptible.
The re-check exists because
Url rewriting is very common, encouraged, and even implemented by certain ImageResizer plugins (such as FolderResizeSyntax and ImageHandlerSyntax).
Url rewriting after the Authorize stage allows UrlAuthorization to be circumvented completely.
HttpContext.SkipAuthorization should be respected by ImageResizer; and probably will be in a future release.
That said, your workaround involving AuthorizeImage is actually exactly what I would suggest. I don't see how it could be more fragile than SkipAuthorization by itself; and in fact should work regardless of how ImageResizer reorders events in the future.
ImageResizer respects the order of events in the pipeline - your V2 with authorization happening before PostAuthorize is exactly correct (although it could be moved to PreAuthorize, if you wished to support additional front-end resizing during BeginRequest).
Also, using RewritePath for serving the original PDF is far more efficient than calling WriteFile, especially on IIS6+, as you probably discovered.