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

Insert Content Security Policy Headers #24

Closed
rpattcorner opened this issue Feb 17, 2020 · 17 comments
Closed

Insert Content Security Policy Headers #24

rpattcorner opened this issue Feb 17, 2020 · 17 comments

Comments

@rpattcorner
Copy link
Contributor

We're working our way thru adapting this promising approach to securing an Angular JS app. In replacing the sample REACT application we are finding our CSS blocked, and it looks as if it's a problem with content security policy headers. There's good information in adding headers here, but because of the complexity of the authentication solution it's not clear where in the chain of requests and responses to add code to create the headers.

Do you have a suggestion or recommendation?

And a request ... it would be great to have a little documentation on discussion on taking the deployed solution to the next step and using it for an actual app, including issues like this.

Thanks for a promising (if complex) solution!

@ottokruse
Copy link
Collaborator

Hi @rpattcorner . When you deploy the stack you can pass in the HTTP headers as a stack parameter. Among the HTTP headers are the CSP headers, that you can alter (or remove). If you've already deployed the stack, you can redeploy it while specifying a new version for the HTTP headers parameter, the redeploy will then work as an update for your stack.

E.g. if you wanna just remove the CSP header for now:

sam deploy \
--template-file packaged.yaml \
--stack-name "${YOUR_STACK_NAME}" \
--capabilities CAPABILITY_IAM \
--parameter-overrides Version=$(date +%s) HttpHeaders='{"Strict-Transport-Security":"max-age=31536000; includeSubdomains; preload","Referrer-Policy":"same-origin","X-XSS-Protection":"1; mode=block","X-Frame-Options":"DENY","X-Content-Type-Options":"nosniff"}' \
--region us-east-1

Let me know if that helps.

More docs would be great, I agree with you totally! Is it something maybe you wanna help out with, as you are going through "taking the deployed solution to the next step" now?

@rpattcorner
Copy link
Contributor Author

Many thanks! Somehow the fact that the CSP was being injected from a visible parm escaped me, and removing the header and updating CFN does indeed remove the failure.

Of course now we need to learn enough about CSP to deploy the correct headers rather than none at all, which is just how things go!

I notice that the update took a very long time (probably a combination of the CFront deploying to all regions and edge invalidation) and, more to interest, when the stack finally showed UPDATE_COMPLETE a number of individual events continue to show as as UPDATE_IN_PROGRESS, for example (HttpHeadersHandlerCodeUpdate, CheckAuthHandlerCodeUpdate) etc. as well as the event on the stack as a whole (CloudFrontDistribution). That's probably a CFN issue, but thought I'd call it out.

You're quite right, I should add to the documentation. At this stage all I have useful to say is how to deploy your own app 'on top of' the full deploy, but even that is probably worth putting in the README, along with your advice on removing CSP headers. Ideally other useful doc might include:

  • A better description of how to deploy a preexisting app, probably using the CreateCloudFrontDistribution parameter that suppresses the bucket and distribution creation
  • A way to use a real domain name, should I ever figure it out
  • Perhaps some naming customization. Although it looks to me like, for example with the S3 bucket name, any attempt to customize collides with a widely-used and existing BucketName reference.

I'd like to see the project become a piece of boilerplate for folks who just want to put some decent authentication in front of a SPA - and see it is close. I've tried a number of other published approaches, which all have not worked, probably because of bitrot.

Again, thanks!

@ottokruse
Copy link
Collaborator

Sounds good. Let's keep this issue open until you've finished your setup, and ran into all the hurdles on your way. Then we can discuss what to add to the docs.

@rpattcorner
Copy link
Contributor Author

rpattcorner commented Feb 24, 2020

OK ... I have an update on the README that documents the CSP mod which is trivial, but I can send a PR for just that bit any time if you want. Next comes doc on trying to deploy on an existing bucket. Then ... maybe someday ... manual work we've carried out creating a Cognito Identity Pool so our app can access an AWS Role. I have the manual steps, but the new serverless build framework is new to me, so may take some time.

I'm getting doubts about Cognito based on this post but for now it's what we're using ... because you did and your app provides so much value!

FUD is ubiquitous but it sounds like the writer had a fair number of concrete instances.

@ottokruse
Copy link
Collaborator

Sure send me that PR on the CSP mod doc please. The smaller the PR's the better.

Regarding deploying to an existing bucket: that's also something I want to make possible in the deployment itself (#20 )

On Cognito: yes it has rough edges - but if you know what you're doing (/have good examples to follow) then you should be good.

@rpattcorner
Copy link
Contributor Author

rpattcorner commented May 9, 2020

@ottokruse

Some progress on controlling the s3 bucket based on the reuse-auth-only.yaml example but the headers issue rears its head again.

The essence of the question I'm seeing is:

How can we use the reuse-auth-template pattern and override a parameter in the underlying SAM application. Specifically the HttpHeaders in this case, but other cases probably exist

From what I can see the reuse-auth-only.yaml template leaves the Lambdas strictly alone, but to accomodate css we need to inject the http headers (without CSP in my case), just as the full deploy does. I'll experiment some, using the full template as a guide, but guidance on how to do so would be very welcome.

What I'm specifically seeing is:

  • As an FYI, the reuse-auth-only.yaml is a little misleading in that it looks like it has a complete CloudFront example in it, but in fact does not, as the OAI is missing.
  • More to the point, once I've modded that template and deployed, external links to stylesheets are failing. My guess is this is the old CSP http header issue, and that's the area I'm looking for direction in

My goal is to produce a version of reuse-auth-only.yaml that will actually run a (SPA mode) deployment while specifying the CDN and S3 artifacts.

The problem seems to be this:

  • The strategy of reuse-auth-only is based on not messing with building from source, but rather incorporating default behaviors, but
  • In this case I need to modify a default behaviour, specifically supplying a set of (CSP-less for now) headers to the HttpHeadersHandler.
  • Doing the obvious thing -- inserting the same kind of code modification stanzas the full template uses -- fails for lack of a proper CodeUri, e.g. src/... won't do with the simpler reuse-auth-only pattern.

It looks like that kind of code modification is present in the full up template, but that it requires a custom cloudformation resource to alter the HttpHeaders lambda's configuration.txt. Is this correct? If so, do you have any suggestions on how to enhance the reuse-only.yaml to enable the custom resource functionality? If not, other ideas?

So, doing this:

Resources:
  LambdaEdgeExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - edgelambda.amazonaws.com
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        
  HttpHeadersHandler:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/lambda-edge/http-headers/
      Handler: bundle.handler
      Runtime: nodejs12.x
      Role: !GetAtt LambdaEdgeExecutionRole.Arn
      Timeout: 5

  ... more resources...

yields this deploy fail:

'CodeUri' is not a valid S3 Uri of the form 's3://bucket/key' with optional versionId query parameter..

In the full up template the Handler's CodeUri points to src/..., which makes sense as you're building in SAM.

Do you suppose there is a way in the reuse-auth-only template there is a way to reference the CodeUri of the default HttpHeadersHandler that is being built behind the scenes as part of the nested stack, so we can pass the proper headers in? Per earlier note I suspect this requires enabling the custom resource in the CFN, or forking, building a SAM and modifying source, which I'd prefer to avoid.

I was stuck awhile because modifying configuration.json had no effect on the deployed app, until I discovered that you have to refresh the trigger, which is nearly invisible in the GUI. But doing so unblocks the desired CSS, so the question comes down to an economical way to enable the reuse template to modify the published configuration.json like its big brother does ...

@rpattcorner
Copy link
Contributor Author

rpattcorner commented May 11, 2020

@ottokruse OK, been quite some time since I've looked at nested stacks and custom resources and much has changed.

I think I understand the pattern shown in the main template, where a custom resource hits a special lambda that modifies others of the lambdas based on params, which is what I want. But I'm missing a detail that will probably be obvious.

I'm wanting (per above) to modify the HttpHandler for a custom configuration for CSP, but within my own stack that consumes the app.

So, I see the app exports the CodeUpdateHandler as well as the HttpHeadersHandler, and I want to do something like this:

  LambdaEdgeProtection:
    Type: AWS::Serverless::Application
    Properties:
      Location:
        ApplicationId: arn:aws:serverlessrepo:us-east-1:520945424137:applications/cloudfront-authorization-at-edge
        SemanticVersion: 1.1.0
      Parameters:
        CreateCloudFrontDistribution: "false"
        # AlternateDomainNames: egt-labs.com


  HttpHeadersHandlerCodeUpdate:
    Type: Custom::LambdaCodeUpdate
    Properties:
      ServiceToken: !GetAtt LambdaEdgeProtection.Outputs.CodeUpdateHandler
      LambdaFunction: !GetAtt LambdaEdgeProtection.Outputs.HttpHeadersHandler
      Version: !Ref Version
      Configuration: !Ref HttpHeaders

Which I'd hoped would use the ServiceToken addtress to invoke the CodeUpdateHandler lambda, telling it to update the HttpHeadersHandler.

But there's some kind of permissions problem. Which you'd think would not be an issue since I'm asking the import of the app to update itself. I get the access denied exception below (account number redacted).

Can you see where I've gone wrong?

Failed to create resource. AccessDeniedException: User: arn:aws:sts::999999999999:assumed-role/jmpr-cupdate-1a-LambdaEdg-LambdaCodeUpdateHandlerR-J6DG2NJSDKDZ/jmpr-cupdate-1a-LambdaEdge-LambdaCodeUpdateHandler-LPKE2OLZ02K4 is not authorized to perform: lambda:GetFunction on resource: arn:aws:lambda:us-east-1:999999999999:function:jmpr-cupdate-1a-LambdaEdgeProte-HttpHeadersHandler-10JN8QX4GCAUE:1 at Object.extractError (/var/runtime/node_modules/aws-sdk/lib/protocol/json.js:51:27) at Request.extractError (/var/runtime/node_modules/aws-sdk/lib/protocol/rest_json.js:55:8) at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:106:20) at Request.emit (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:78:10) at Request.emit (/var/runtime/node_modules/aws-sdk/lib/request.js:683:14) at Request.transition (/var/runtime/node_modules/aws-sdk/lib/request.js:22:10) at AcceptorStateMachine.runTo (/var/runtime/node_modules/aws-sdk/lib/state_machine.js:1

@ottokruse
Copy link
Collaborator

ottokruse commented May 11, 2020

My goal is to produce a version of reuse-auth-only.yaml that will actually run a (SPA mode) deployment while specifying the CDN and S3 artifacts.

Awesome.

About the HTTP headers, can you not do it like this?:

LambdaEdgeProtection:
    Type: AWS::Serverless::Application
    Properties:
      Location:
        ApplicationId: arn:aws:serverlessrepo:us-east-1:520945424137:applications/cloudfront-authorization-at-edge
        SemanticVersion: 1.1.0
      Parameters:
        CreateCloudFrontDistribution: "false"
        # AlternateDomainNames: egt-labs.com
        HttpHeaders: yourstuffhere

Can you see where I've gone wrong?

If you go the custom resource route (which I hope you do not have to) you need to give the Lambda handler of your custom resource permission to read and update the Lambda you want it to update. Did you do that?

@rpattcorner
Copy link
Contributor Author

Ah, so close and yet so far. Never occurred to me to pass the HttpHeaders directly to the app. That seems to be accepted by CFN.

So we get a full CFN run, and inspection shows that the HttpHeaders lambda has the correct configuration.json. Yet on run I see:

503 ERROR
The request could not be satisfied.
The Lambda function associated with the CloudFront distribution is invalid or doesn't have the required permissions. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
Generated by cloudfront (CloudFront)
Request ID: c_PxcPfB_juqg2TVppHbTWsbcsUW3jOQULtO6N0EJT7hJMoa42TVdw==

Which is like nothing I ever saw, and incredibly vague. Any hints?

If I have to go back to the (clumsier) custom resource and adjust permissions, it sounds like you're saying I need to give the HttpHeadersHandlerCodeUpdate: resource above the permission to modify the actual HttpHeadersHandler in the nested stack. Did you mean in a similar manner to the snippet below from the full template?

  LambdaCodeUpdateHandler:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/cfn-custom-resources/lambda-code-update/
      Handler: index.handler
      Runtime: nodejs12.x
      Policies:
        - Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Action:
                - lambda:GetFunction
                - lambda:UpdateFunctionCode
              Resource:
                - !GetAtt ParseAuthHandler.Arn
                - !GetAtt CheckAuthHandler.Arn
                - !GetAtt HttpHeadersHandler.Arn
                - !GetAtt RefreshAuthHandler.Arn
                - !GetAtt SignOutHandler.Arn

@rpattcorner
Copy link
Contributor Author

rpattcorner commented May 11, 2020

The 503 looks like this item on the LambdaEdgePr-UserPoolDomainHandler. Never been a problem before. If you've seen it, let me know!


12:36:27
2020-05-11T12:36:27.256Z e9bae167-9a46-45ec-9f89-136fad11abb7 ERROR Can't parse physicalResourceId: undefined
2020-05-11T12:36:27.256Z	e9bae167-9a46-45ec-9f89-136fad11abb7	ERROR	Can't parse physicalResourceId: undefined

all I can find in the (cfn) doc is this:

physicalResourceId

    Optional. The unique identifier of the custom resource that invoked the function. By default, the module uses the name of the Amazon CloudWatch Logs log stream that's associated with the Lambda function.

@ottokruse
Copy link
Collaborator

Sorry mate you run into every corner case possible it seems.

Is it an option for you to start development in a brand new stack? I think the errors you get now might be caused by having deployed the same stack several times, using different versions.

We should really get the "simple" option working for you, to inject the HTTP headers as app params. That should work - and if it does not, that is a bug we should fix.

@rpattcorner
Copy link
Contributor Author

Yes, this is challenging. Help is welcome.

I am deploying in a new stack each time ... not solid on stack updates so I've been avoiding them. A new deploy seems to still bring the 503, so no idea how to proceed.

The interactions between the CloudFront and Cognito have always been solid until this last injection of a parameter into the the application. Can't imagine what's changing.

Probably near the end of your day, but thoughts welcome

@ottokruse
Copy link
Collaborator

Can you paste your CFN template here? Then I can reproduce

@rpattcorner
Copy link
Contributor Author

rpattcorner commented May 11, 2020

May or may not be relevant ... I see in logs:

@timestamp
1589211025611
errorMessage
SyntaxError: Unexpected end of JSON input
errorType
Runtime.UserCodeSyntaxError
stack.0
Runtime.UserCodeSyntaxError: SyntaxError: Unexpected end of JSON input
stack.1
at _loadUserApp (/var/runtime/UserFunction.js:98:13)
stack.2
at Object.module.exports.load (/var/runtime/UserFunction.js:140:17)
stack.3
at Object.<anonymous> (/var/runtime/index.js:43:30)
stack.4
at Module._compile (internal/modules/cjs/loader.js:1156:30)
stack.5
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1176:10)
stack.6
at Module.load (internal/modules/cjs/loader.js:1000:32)
stack.7
at Function.Module._load (internal/modules/cjs/loader.js:899:14)
stack.8
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)
stack.9
at internal/main/run_main_module.js:18:47

Doublechecking my headers param to be sure I'm not screwing up the JSON

@rpattcorner
Copy link
Contributor Author

rpattcorner commented May 11, 2020

Yeah, that's it. Many thanks for the direction ... the problem was indeed a bad default in the json parameter that HttpHeaders silently applies to the request to checkAuth, messing it up mightily.

Hopefully that will get us doing, and get the team a nice new PR with documentation. The reuse-auth-only doesn't have a noSPA example, which is just as well, since I don't have testing for it, but we should have a SPA example shortly.

@rpattcorner
Copy link
Contributor Author

OK, PR's in. Thanks for the help!

@ottokruse
Copy link
Collaborator

Thanks for your help!

Closing this one as we have merged #55

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants