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

feat: add cognito idp example with facebook integration #298

Closed
wants to merge 29 commits into from

Conversation

ericzbeard
Copy link
Contributor

This example uses the new UserPoolIdentityProvider to create a REST API that controls access via Cognito and the Facebook IDP. It also has L3 constructs for the creation of a static site and for configuring Lambda API handlers.

See aws/aws-cdk#8134

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@nija-at nija-at assigned ericzbeard and unassigned ericzbeard and nija-at Jun 8, 2020
@nija-at nija-at self-requested a review June 8, 2020 11:30
@nija-at nija-at added the contribution/core This is a PR that came from AWS. label Jun 8, 2020
nija-at
nija-at previously requested changes Jun 8, 2020
Copy link
Contributor

@nija-at nija-at 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 about the setup before I go deeper into the implementation.

Comment on lines 38 to 40
# cdk synth breaks in examples that rely on environment variables
if [[ -f DO_NOT_AUTOSYNTH ]]; then exit 0; fi

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we avoid? Synthesizing during the build is the validation that none of the code here has broken due to changes in the main CDK library.

This is especially true/useful for ones that use experimental APIs (viz Cognito).

Copy link
Contributor

Choose a reason for hiding this comment

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

What is stopping us from making this a synthesizeable stack?

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 use a number of environment variables, and it's important for the stack to fail if any of them are not set, to avoid later errors that are harder to troubleshoot. It might be possible to replace some of them with parameter store, but this complicates setup, and I'm not sure if it's practical to get rid of all of them. I will look into that today.

Comment on lines 9 to 17
const region = util.getEnv('AWS_REGION');
const account = util.getEnv('AWS_ACCOUNT');

const app = new cdk.App();
const stack = new CognitoIdpStack(app, 'CognitoIdpStack', {
env: {
account,
region
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we leave out region and account as optional and let the defaults do their magic?

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 thought this was best practice? I seem to remember an error or warning at some point for not setting these.

Copy link
Contributor

Choose a reason for hiding this comment

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

An error will occur if CDK can’t produce an environment agnostic template. Personally I leave these out as a way of letting me know when I’ve crossed that threshold.

Comment on lines 32 to 47
Put the following into a .env file in the root folder for this example, replacing each placeholder value with your specific environment values.

```
AWS_REGION=us-east-1
AWS_ACCOUNT=123456789123
WEB_DOMAIN=myapp.example.com
API_DOMAIN=myapi.example.com
WEB_CERTIFICATE_ARN=
API_CERTIFICATE_ARN=
FACEBOOK_APP_ID=
FACBOOK_VERSION=v7.0
FACEBOOK_SECRET_ARN=
COGNITO_REDIRECT_URI=
COGNITO_DOMAIN_PREFIX=
COGNITO_REGION=
```
Copy link
Contributor

Choose a reason for hiding this comment

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

Since you've modeled this whole set up as a Stack, can we make these options part of the stack's Props argument instead? This will make this in line with the intended use of CDK constructs and stacks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes I can do that, but maybe I should wait until we decide the best way to handle environment variables.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would these be best located in the CDK context variable file? Instead of external environment variables?

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe I should wait until we decide the best way to handle environment variables.

What is the pending decision you're referring to?

Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose you're referring to the comment here - #298 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How would that work on a team, though? Each developer would need their own values for each of those. And then dev, beta, gamma, prod environments would all need their own values.

Copy link

Choose a reason for hiding this comment

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

Our stance in general is that “degrees of freedom” should generally be expressed through code APIs and fully resolved during synthesis so that synthesis is “pure” and remains deterministic. The invariant is that given a commit in the repo, the resulting cloud assembly will always be exactly the same for any call of “synth”. Since CDK apps are executed during build, this is a similar invariant that compilers have and we have assumptions in our framework that rely on this, and generally a best practice from an operational standpoint. That’s also the reason we discourage querying external APIs during synthesis.

We should also consider the fact that this is an “official” example. As such it should not encourage anti patterns.

Having said all that, the development use case is slightly different, because as @ericzbeard noted, each developer would have their own environment and those values will be different for each developer, in which case I do think that environment variables are a viable approach, but there are numerous other solutions that can be used and perhaps will be easier to work with.

For example, the code can read those values from a simple json file. By default it can read the “production” file from the project directory and an environment variable can be used to point it to a different file, for each developer.

Here is what I propose:

Define a MyStackProps interface with all these attributes and read its contents from a json file. Commit a file to source control with dummy “production” values (normally these would be the actual production values but since this is a sample, we will just put some dummy values).

Allow the developer to override the file’s location through an environment variable.

Sketch:

const configFile = process.env.CONFIG_FILE ?? ./config.json”;
new MyStack(app, “stack”, require(configFile));

What do you guys think? We know we need to publish more guidance on this topic and we even have an rfc tracking item for that, but never got around to it. @ericzbeard help!

Copy link
Contributor

Choose a reason for hiding this comment

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

I like it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change won't be too hard to implement and I suppose it's more idiomatic for CDK, but I'm not sure if it's what developers will expect. A .env file is an extremely common and well-understood pattern.

I'm also not sure about checking in a "production" config file. I have always seen checking in config values for any environment as an anti-pattern. Especially if eventually the checked in config file has actual production values and not placeholders - that could potentially cause outages if developers have privileges to the production system.

I often create a sample config file for reference and check that in, or simply document it in a README. I suppose if we want the project to build and synth with no additional action from a user (and for the purposes of an automated CI server) we don't have a choice, but I worry about a user cloning the project, building, and attempting to deploy it without creating their own config file. That will definitely fail and might be confusing. As it is, synth fails with a clear message that you are missing a necessary environment variable. How do we solve that problem?

Once we figure this out, it should definitely be built into the CLI somehow, probably using context variables. We already have cdk.json but that gets checked in. Maybe we should have a cdk.local.json that gets picked up automatically if present and is added to the .gitignore file when creating a new app?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the time being, I committed a change to move all environment variable loading to the file in bin/ where we create the app. The stack no longer has any direct references to .env, and uses props that are passed in. This still leaves several open questions from above.

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the purpose of this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just a quick way for me to build and deploy during development.

"cognito-test": "jest --group=cognito",
"api-test": "jest --group=api",
"handler-test": "jest --group=handler",
"deploy": "cdk deploy --require-approval never",
Copy link
Contributor

Choose a reason for hiding this comment

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

Would be better to drop this script and ask the user to run cdk deploy instead.

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 find that to be super annoying during development. Can't tell you how many times I have run cdk deploy and then took a break, and then I come back 15 minutes later and see the confirmation prompt waiting for me.

"api-test": "jest --group=api",
"handler-test": "jest --group=handler",
"deploy": "cdk deploy --require-approval never",
"link-cdk": "../../../aws-cdk-fork/link-all.sh"
Copy link
Contributor

Choose a reason for hiding this comment

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

Drop

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Did you mean drop all 4 lines? Or just one of them? I removed link-cdk.

Co-authored-by: Niranjan Jayakar <nija@amazon.com>
@mergify mergify bot dismissed nija-at’s stale review June 8, 2020 14:14

Pull request has been modified.

eladb
eladb previously requested changes Jun 10, 2020
Copy link

@eladb eladb left a comment

Choose a reason for hiding this comment

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

Initial comments. Haven’t reviewed deeply yet

let contents = '';

if (requestType === 'Create' || requestType === 'Update') {
const configFileName = 'config.js';
Copy link

Choose a reason for hiding this comment

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

Indentation should be 2 spaces.

/**
* Convert a DynamoDB user to a User object.
*/
userConvert(item: any): User {
Copy link

Choose a reason for hiding this comment

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

Run tslint/eslint

fs.ensureDirSync('functions/node_modules');

fs.emptyDirSync('dist');

Copy link

Choose a reason for hiding this comment

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

I would recommend moving all build/dev related scripts/tools to a “tools” or “scripts” directory so they won’t mix up with the actual app.

import { APIGatewayEvent } from 'aws-lambda';
import { Handler } from './handler';
import * as AWS from 'aws-sdk';
import { APIEventResponse } from './handler';
Copy link

Choose a reason for hiding this comment

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

Rename the functions directory to lambda (that has been our convention so far, see cdkworkshop.com as an example)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed these comments in my latest commit.

@mergify mergify bot dismissed eladb’s stale review June 11, 2020 19:29

Pull request has been modified.

@ericzbeard
Copy link
Contributor Author

Now that 1.46 is released this PR is building, but I'm still not happy with how I'm handling environment variables. I was not able to use a placeholder "prod" stack since context lookups need a real account with credentials. I ended up removing the context file, since it had configuration values from my local development environment, which I didn't want to commit. And I'm basically omitting synth on the build server, which is probably not a good practice. @nija-at @eladb any suggestions?

How do we handle a scenario where a developer who does not have access to prod, etc, wants to add resources that require a context lookup? And how do we handle an example like this where it's not possible to build/synth in a meaningful way without local environment variables?

Still learning!

@nija-at
Copy link
Contributor

nija-at commented Jun 23, 2020

How do we handle a scenario where a developer who does not have access to prod, etc, wants to add resources that require a context lookup?

Can you point me to where you're doing this in your code so I can better understand what you're looking to do?

how do we handle an example like this where it's not possible to build/synth in a meaningful way without local environment variables?

I suppose the following lines are what you're referring to here.

I suppose if we want the project to build and synth with no additional action from a user (and for the purposes of an automated CI server) we don't have a choice, but I worry about a user cloning the project, building, and attempting to deploy it without creating their own config file. That will definitely fail and might be confusing. As it is, synth fails with a clear message that you are missing a necessary environment variable. How do we solve that problem?

This isn't a problem we should be solving here. The examples repo is simply providing a set of "examples" for users to get inspiration from. Any code that is copy-pasted/cloned should be done with understanding that this is not production code and will need modification and line-by-line review.
The example repo is not vending a set of CDK templates.

@ericzbeard
Copy link
Contributor Author

@nija-at See bin/congito-idp.ts

@ericzbeard
Copy link
Contributor Author

In my latest commit I think I have something that's satisfactory. I changed how I was referencing the hosted zone so I don't actually need a context lookup, so the prod stack with fake properties synthesizes Ok. I have a better understanding of context lookups now and why we need them, but it seems best to avoid them if possible. @eladb @nija-at

@eladb
Copy link

eladb commented Jun 24, 2020

In my latest commit I think I have something that's satisfactory. I changed how I was referencing the hosted zone so I don't actually need a context lookup, so the prod stack with fake properties synthesizes Ok. I have a better understanding of context lookups now and why we need them, but it seems best to avoid them if possible. @eladb @nija-at

I agree that if those can be avoided, the better.

@nija-at
Copy link
Contributor

nija-at commented Jun 25, 2020

@ericzbeard - I see you still have some code for skipping synth. Re-request a review when you've cleaned this up and is ready for another round.

@ericzbeard
Copy link
Contributor Author

@nija-at Done.

Copy link
Contributor

@nija-at nija-at left a comment

Choose a reason for hiding this comment

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

As I go through this PR, I feel more strongly now that this should not be part of the aws-cdk-examples repo. We should move this into a separate repository under aws-samples. We can of course add links to the repo from other locations.

Apps under the CDK examples repos showcase non-trivial use cases of the CDK or integrations across AWS services. It has insofar not carried a full blown app.
This code here not just defines infra via CDK but also application logic, opinionated ways to set up the application, projects and organize lambda functions.

This will be probably the only CDK application is this repository of its kind. I don't expect more of these to land here. This variation is likely going to be harder to maintain (if it breaks or needs to fixed up) and keep consistent with the rest of the examples as we evolve this repository.

Finally, this is a massive PR! 70 files, with each file being ~100 lines, that's a ballpark of 7000 LOC which becomes impossible to effectively do any kind of review (I've spent an hour total on it so far and don't feel like I'm able to effectively continue). Separating this into its own repo significantly lowers the cost of reviewing such a repo and trust that it works.

@eladb - what do you think of this? How hard is it to create a new repo in this GH org?

@eladb
Copy link

eladb commented Jun 28, 2020

I tend to agree that this feels like it deserves it's own repo. It will be much easier to review/maintain this way.

what do you think of this? How hard is it to create a new repo in this GH org?

Very easy. We have self-service resources for that internally.

@richardhboyd
Copy link
Contributor

might also be a good time to document this decision in something like this #177

@ericzbeard
Copy link
Contributor Author

@ericzbeard ericzbeard closed this Aug 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contribution/core This is a PR that came from AWS.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants