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

Support for non-LE SSL Certs and non-Route53 DNS providers #659

Merged
merged 4 commits into from
Feb 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,9 @@ to change Zappa's behavior. Use these at your own risk!
},
"cache_cluster_enabled": false, // Use APIGW cache cluster (default False)
"cache_cluster_size": 0.5, // APIGW Cache Cluster size (default 0.5)
"certificate": "my_cert.crt", // SSL certificate file location. Used to manually certify a custom domain
"certificate_key": "my_key.key", // SSL key file location. Used to manually certify a custom domain
"certificate_chain": "my_cert_chain.pem", // SSL certificate chain file location. Used to manually certify a custom domain
"cloudwatch_log_level": "OFF", // Enables/configures a level of logging for the given staging. Available options: "OFF", "INFO", "ERROR", default "OFF".
"cloudwatch_data_trace": false, // Logs all data about received events.
"cloudwatch_metrics_enabled": false, // Additional metrics for the API Gateway.
Expand Down Expand Up @@ -485,6 +488,7 @@ to change Zappa's behavior. Use these at your own risk!
"project_name": "MyProject", // The name of the project as it appears on AWS. Defaults to a slugified `pwd`.
"remote_env": "s3://my-project-config-files/filename.json", // optional file in s3 bucket containing a flat json object which will be used to set custom environment variables.
"role_name": "MyLambdaRole", // Name of Zappa execution role. Default ZappaExecutionRole. To use a different, pre-existing policy, you must also set manage_roles to false.
"route53_enabled": true, // Have Zappa update your Route53 Hosted Zones when certifying with a custom domain
"s3_bucket": "dev-bucket", // Zappa zip bucket,
"slim_handler": false, // Useful if project >50M. Set true to just upload a small handler to Lambda and load actual project from S3 at runtime.
"settings_file": "~/Projects/MyApp/settings/dev_settings.py", // Server side settings file location,
Expand Down Expand Up @@ -618,20 +622,14 @@ If you want to use Zappa on a domain with a free Let's Encrypt certificate using

However, it's now far easier to use Route 53-based DNS authentication, which will allow you to use a Let's Encrypt certificate with a single `$ zappa certify` command.

##### Deploying with Custom Domain Name (with your own SSL Certs)
##### Deploying to Domain Name with your own SSL Certs

1. The first step is to create a custom domain and upload your SSL cert / key / bundle - follow this guide [Set Up a Custom Domain Name for an API Gateway API](http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html#how-to-custom-domains-console)
1. The first step is to create a custom domain and obtain your SSL cert / key / bundle.
2. Ensure you have set the `domain` setting within your Zappa settings JSON - this will avoid problems with the Base Path mapping between the Custom Domain and the API invoke URL, which gets the Stage Name appended in the URI
3. Deploy or update your app using Zappa
4. Create a base path mapping between your custom domain name and your chosen API stage, leaving the base-path blank if you wish to access your app on the root path of your custom domain e.g. myapp.com rather than myapp.com/prod

`$ aws apigateway create-base-path-mapping --domain-name myapp.com --rest-api-id 123abc --stage prod --base-path '' --region us-west-2`

Ensure you have a CNAME to resolve your custom domain name to the CloudFront Distribution domain name which can be found using:

`$ aws apigateway get-domain-names`

There is an [open ticket](https://github.com/Miserlou/Zappa/issues/401) to automate this process.
3. Add the paths to your SSL cert / key / bundle to the `certificate`, `certificate_key`, and `certificate_chain` settings, respectively, in your Zappa settings JSON
4. Set `route53_enabled` to `false` if you plan on using your own DNS provider, and not an AWS Route53 Hosted zone.
5. Deploy or update your app using Zappa
6. Run `$ zappa certify` to upload your certificates and register the custom domain name with your API gateway.

#### Setting Environment Variables

Expand Down
19 changes: 18 additions & 1 deletion tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,9 @@ def get_domain_name(self, domain):
def create_domain_name(self, *args, **kw):
self.calls.append(("create_domain_name", args, kw))

def update_route53_records(self, *args, **kw):
self.calls.append(("update_route53_records", args, kw))

def update_domain_name(self, *args, **kw):
self.calls.append(("update_domain_name", args, kw))

Expand Down Expand Up @@ -1134,8 +1137,9 @@ def update_domain_name(self, *args, **kw):
})
sys.stdout.truncate(0)
zappa_cli.certify(no_cleanup=True)
self.assertEquals(len(zappa_cli.zappa.calls), 1)
self.assertEquals(len(zappa_cli.zappa.calls), 2)
self.assertTrue(zappa_cli.zappa.calls[0][0] == "create_domain_name")
self.assertTrue(zappa_cli.zappa.calls[1][0] == "update_route53_records")
log_output = sys.stdout.getvalue()
self.assertIn("Created a new domain name", log_output)

Expand All @@ -1147,6 +1151,19 @@ def update_domain_name(self, *args, **kw):
self.assertTrue(zappa_cli.zappa.calls[0][0] == "update_domain_name")
log_output = sys.stdout.getvalue()
self.assertNotIn("Created a new domain name", log_output)

# Test creating domain without Route53
zappa_cli.zappa_settings["stage"].update({
"route53_enabled": False,
})
zappa_cli.zappa.calls = []
zappa_cli.zappa.domain_names["test.example.com"] = ""
sys.stdout.truncate(0)
zappa_cli.certify(no_cleanup=True)
self.assertEquals(len(zappa_cli.zappa.calls), 1)
self.assertTrue(zappa_cli.zappa.calls[0][0] == "create_domain_name")
log_output = sys.stdout.getvalue()
self.assertIn("Created a new domain name", log_output)
finally:
sys.stdout = old_stdout

Expand Down
24 changes: 14 additions & 10 deletions zappa/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1348,7 +1348,8 @@ def certify(self, no_cleanup=False):

if not cert_location:
if not account_key_location:
raise ClickException("Can't certify a domain without " + click.style("lets_encrypt_key", fg="red", bold=True) + " configured!")
raise ClickException("Can't certify a domain without " + click.style("lets_encrypt_key", fg="red", bold=True) +
" or " + click.style("certificate", fg="red", bold=True) + " configured!")

if account_key_location.startswith('s3://'):
bucket, key_name = parse_s3_url(account_key_location)
Expand Down Expand Up @@ -1384,9 +1385,18 @@ def certify(self, no_cleanup=False):
domain,
clean_up
)

# Deliberately undocumented feature (for now, at least.)
# We are giving the user the ability to shoot themselves in the foot.
# _This is probably not a good idea._
# However, I am sick and tired of hitting the Let's Encrypt cert
# limit while testing.
if clean_up:
cleanup()

else:
if not self.zappa.get_domain_name(domain):
self.zappa.create_domain_name(
dns_name = self.zappa.create_domain_name(
domain,
domain + "-Zappa-Cert",
certificate_body,
Expand All @@ -1395,6 +1405,8 @@ def certify(self, no_cleanup=False):
self.lambda_name,
self.api_stage
)
if self.stage_config.get('route53_enabled', True):
self.zappa.update_route53_records(domain, dns_name)
print("Created a new domain name. Please note that it can take up to 40 minutes for this domain to be "
"created and propagated through AWS, but it requires no further work on your part.")
else:
Expand All @@ -1408,14 +1420,6 @@ def certify(self, no_cleanup=False):

cert_success = True

# Deliberately undocumented feature (for now, at least.)
# We are giving the user the ability to shoot themselves in the foot.
# _This is probably not a good idea._
# However, I am sick and tired of hitting the Let's Encrypt cert
# limit while testing.
if clean_up:
cleanup()

if cert_success:
click.echo("Certificate " + click.style("updated", fg="green", bold=True) + "!")
else:
Expand Down
15 changes: 10 additions & 5 deletions zappa/zappa.py
Original file line number Diff line number Diff line change
Expand Up @@ -1459,7 +1459,7 @@ def create_domain_name(self,
lambda_name,
stage):
"""
Great the API GW domain.
Create the API GW domain and returns the resulting dns_name
"""

agw_response = self.apigateway_client.create_domain_name(
Expand All @@ -1470,20 +1470,25 @@ def create_domain_name(self,
certificateChain=certificate_chain
)

dns_name = agw_response['distributionDomainName']
zone_id = self.get_hosted_zone_id_for_domain(domain_name)

api_id = self.get_api_id(lambda_name)
if not api_id:
raise LookupError("No API URL to certify found - did you deploy?")

response = self.apigateway_client.create_base_path_mapping(
self.apigateway_client.create_base_path_mapping(
domainName=domain_name,
basePath='',
restApiId=api_id,
stage=stage
)

return agw_response['distributionDomainName']

def update_route53_records(self, domain_name, dns_name):
"""
Updates Route53 Records following GW domain creation
"""
zone_id = self.get_hosted_zone_id_for_domain(domain_name)

is_apex = self.route53.get_hosted_zone(Id=zone_id)['HostedZone']['Name'][:-1] == domain_name
if is_apex:
record_set = {
Expand Down