Skip to content

Commit 32190b0

Browse files
authored
HTTPS setup (#1069)
1 parent 83910d3 commit 32190b0

File tree

17 files changed

+271
-9
lines changed

17 files changed

+271
-9
lines changed

cli/cmd/lib_cluster_config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,11 @@ func setConfigFieldsFromCached(userClusterConfig *clusterconfig.Config, cachedCl
273273
}
274274
userClusterConfig.AvailabilityZones = cachedClusterConfig.AvailabilityZones
275275

276+
if s.Obj(cachedClusterConfig.SSLCertificateARN) != s.Obj(userClusterConfig.SSLCertificateARN) {
277+
return clusterconfig.ErrorConfigCannotBeChangedOnUpdate(clusterconfig.SSLCertificateARNKey, cachedClusterConfig.SSLCertificateARN)
278+
}
279+
userClusterConfig.SSLCertificateARN = cachedClusterConfig.SSLCertificateARN
280+
276281
if userClusterConfig.InstanceVolumeSize != cachedClusterConfig.InstanceVolumeSize {
277282
return clusterconfig.ErrorConfigCannotBeChangedOnUpdate(clusterconfig.InstanceVolumeSizeKey, cachedClusterConfig.InstanceVolumeSize)
278283
}
@@ -505,6 +510,10 @@ func clusterConfigConfirmaionStr(clusterConfig clusterconfig.Config, awsCreds AW
505510
items.Add(clusterconfig.MinInstancesUserKey, *clusterConfig.MinInstances)
506511
items.Add(clusterconfig.MaxInstancesUserKey, *clusterConfig.MaxInstances)
507512
items.Add(clusterconfig.TagsKey, s.ObjFlatNoQuotes(clusterConfig.Tags))
513+
if clusterConfig.SSLCertificateARN != nil {
514+
items.Add(clusterconfig.SSLCertificateARNKey, *clusterConfig.SSLCertificateARN)
515+
}
516+
508517
if clusterConfig.InstanceVolumeSize != defaultConfig.InstanceVolumeSize {
509518
items.Add(clusterconfig.InstanceVolumeSizeUserKey, clusterConfig.InstanceVolumeSize)
510519
}

docs/cluster-management/config.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ tags: # <string>: <string> map of key/value pairs
7272
# whether to use spot instances in the cluster (default: false)
7373
# see https://cortex.dev/v/master/cluster-management/spot-instances for additional details on spot configuration
7474
spot: false
75+
76+
# see https://cortex.dev/v/master/guides/subdomain-https-setup for instructions on how to set up HTTPS for APIs
77+
ssl_certificate_arn: # if empty, APIs will still be accessible via HTTPS (in addition to HTTP), but will not use a trusted certificate
7578
```
7679
7780
The default docker images used for your Predictors are listed in the instructions for [system packages](../deployments/system-packages.md), and can be overridden in your [API configuration](../deployments/api-configuration.md).

docs/cluster-management/uninstall.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,7 @@ aws s3 rb --force s3://<bucket>
4242
# delete the log group (replace <log_group> with what was configured during installation, default: cortex)
4343
aws logs describe-log-groups --log-group-name-prefix=<log_group> --query logGroups[*].[logGroupName] --output text | xargs -I {} aws logs delete-log-group --log-group-name {}
4444
```
45+
46+
If you've setup API gateway and want to delete it, please follow these [instructions](../guides/api-gateway.md#cleanup).
47+
48+
If you've configured HTTPS by specifying an SSL Certificate for a subdomain in your cluster configuration, you may wish to remove the SSL Certificate and Hosted Zone for the domain by following these [instructions](../guides/subdomain-https-setup.md#cleanup).

docs/cluster-management/update.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,5 @@ cortex cluster up
3434
```
3535

3636
In production environments, you can upgrade your cluster without downtime if you have a service in front of your Cortex cluster (for example, you can [configure API Gateway as a proxy service](../guides/api-gateway.md)): first spin up your new cluster, then update your client-facing service to route traffic to your new cluster, and then spin down your old cluster.
37+
38+
If you've set up HTTPS by specifying an SSL Certificate for a subdomain in your cluster configuration, you can upgrade your cluster with minimal downtime: first spin up a new cluster, then update the A record in your subdomain hosted zone to point to the API loadbalancer of your new cluster. Wait at least 60 seconds before spinning down the old cluster because DNS entries for loadbalancers refresh every 60 seconds (see [here](https://docs.aws.amazon.com/elasticloadbalancing/latest/userguide/how-elastic-load-balancing-works.html#request-routing) for more details).
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# Set up HTTPS on a subdomain
2+
3+
_WARNING: you are on the master branch, please refer to the docs on the branch that matches your `cortex version`_
4+
5+
The recommended way to set up HTTPS with trusted certificates is by using [API Gateway](../api-gateway.md) because it's simpler and enables you to use API Gateway features such as rate limiting (it also supports custom domains). This guide is only recommended if HTTPS is required and you don't wish to use API Gateway (e.g. it doesn't support your use case due to limitations such as the 29 second request timeout).
6+
7+
This guide will demonstrate how to create a dedicated subdomain in AWS Route 53 and use an SSL certificate provisioned by AWS Certificate Manager (ACM) to support HTTPS traffic to Cortex APIs. By the end of this guide, you will have a Cortex cluster with APIs accessible via `https://<your-subdomain>/<api-endpoint>`.
8+
9+
You must own a domain and be able to modify its DNS records.
10+
11+
## Step 1
12+
13+
Decide on a subdomain that you want to dedicate to Cortex APIs. For example if your domain is `example.com`, a valid subdomain can be `api.example.com`.
14+
15+
This guide will use `cortexlabs.dev` as the example domain and `api.cortexlabs.dev` as the subdomain.
16+
17+
## Step 2
18+
19+
We will set up a hosted zone on Route 53 to manage the DNS records for the subdomain. Go to the [Route 53 console](https://console.aws.amazon.com/route53/home) and click "Hosted Zones".
20+
21+
![step 2](https://user-images.githubusercontent.com/4365343/82210754-a6b07d00-98dd-11ea-9cec-9f6b07282aa8.png)
22+
23+
## Step 3
24+
25+
Click "Create Hosted Zone" and then enter your subdomain as the domain name for your hosted zone and click "Create".
26+
27+
![step 3](https://user-images.githubusercontent.com/4365343/82211091-4968fb80-98de-11ea-8ec4-8d26d1aea77a.png)
28+
29+
## Step 4
30+
31+
Take note of the values in the NS record.
32+
33+
![step 4](https://user-images.githubusercontent.com/4365343/82211656-386cba00-98df-11ea-8c86-4961082b5f49.png)
34+
35+
## Step 5
36+
37+
Navigate to your root DNS service provider (e.g. Google Domains, AWS Route 53, Go Daddy). Your root DNS service provider is typically the registrar where you purchased your domain (unless you have transferred DNS management elsewhere). The procedure for adding DNS records may vary based on your service provider.
38+
39+
We are going to add an NS (name server) record that specifies that any traffic to your subdomain should use the name servers of your hosted zone in Route 53 for DNS resolution.
40+
41+
`cortexlabs.dev` is managed by Google Domains. The image below is a screenshot for adding a DNS record in Google Domains (your UI may differ based on your DNS service provider).
42+
43+
![step 5](https://user-images.githubusercontent.com/4365343/82211959-bcbf3d00-98df-11ea-834d-692b3bcf9332.png)
44+
45+
## Step 6
46+
47+
We are now going to create an SSL certificate for your subdomain. Go to the [ACM console](https://us-west-2.console.aws.amazon.com/acm/home) and click "Get Started" under the "Provision certificates" section.
48+
49+
![step 6](https://user-images.githubusercontent.com/4365343/82202340-c04ac800-98cf-11ea-9472-89dd6d67eb0d.png)
50+
51+
## Step 7
52+
53+
Select "Request a public certificate" and then "Request a certificate".
54+
55+
![step 7](https://user-images.githubusercontent.com/4365343/82202654-3e0ed380-98d0-11ea-8c57-025f0b69c54f.png)
56+
57+
## Step 8
58+
59+
Enter your subdomain and then click "Next".
60+
61+
![step 8](https://user-images.githubusercontent.com/4365343/82224652-1cbedf00-98f2-11ea-912b-466cee2f6e25.png)
62+
63+
## Step 9
64+
65+
Select "DNS validation" and then click "Next".
66+
67+
![step 9](https://user-images.githubusercontent.com/4365343/82205311-66003600-98d4-11ea-90e3-da7e8b0b2b9c.png)
68+
69+
## Step 10
70+
71+
Add tags for searchability (optional) then click "Review".
72+
73+
![step 10](https://user-images.githubusercontent.com/4365343/82206485-52ee6580-98d6-11ea-95a9-1d0ebafc178a.png)
74+
75+
## Step 11
76+
77+
Click "Confirm and request".
78+
79+
![step 11](https://user-images.githubusercontent.com/4365343/82206602-84ffc780-98d6-11ea-9f2f-ce383404ec67.png)
80+
81+
## Step 12
82+
83+
Click "Create record in Route 53". A popup will appear indicating that a Record is going to be added to Route 53. Click "Create" to automatically add the DNS record to your subdomain's hosted zone. Then click "Continue".
84+
85+
![step 12](https://user-images.githubusercontent.com/4365343/82223539-c8ffc600-98f0-11ea-93a2-044aa0c9670d.png)
86+
87+
## Step 13
88+
89+
Wait for the Certificate Status to be "issued". This might take a few minutes.
90+
91+
![step 13](https://user-images.githubusercontent.com/4365343/82209663-a616e700-98db-11ea-95cb-c6efedadb942.png)
92+
93+
## Step 14
94+
95+
Take note of the certificate's ARN. The certificate is ineligible for renewal because it is currently not being used. It will be eligible for renewal after it is used in Cortex.
96+
97+
![step 14](https://user-images.githubusercontent.com/4365343/82222684-9e613d80-98ef-11ea-98c0-5a20b457f062.png)
98+
99+
## Step 15
100+
101+
Add the following field to your cluster configuration:
102+
103+
```yaml
104+
# cluster.yaml
105+
106+
...
107+
108+
ssl_certificate_arn: <ARN of your certificate>
109+
```
110+
111+
and then create a Cortex cluster.
112+
113+
```bash
114+
$ cortex cluster up --config cluster.yaml
115+
```
116+
117+
## Step 16
118+
119+
After your cluster has been created, navigate to your [EC2 Load Balancer console](https://us-west-2.console.aws.amazon.com/ec2/v2/home#LoadBalancers:sort=loadBalancerName) and locate the Cortex API load balancer. You can determine which is the API load balancer by inspecting the `kubernetes.io/service-name` tag.
120+
121+
Take note of the load balancer's name.
122+
123+
![step 16](https://user-images.githubusercontent.com/808475/80142777-961c1980-8560-11ea-9202-40964dbff5e9.png)
124+
125+
## Step 17
126+
127+
Go to the hosted zone you created in the [Route 53 console](https://console.aws.amazon.com/route53/home#hosted-zones:) and add an Alias record that routes traffic to your Cortex cluster's API load balancer (leave "Name" blank).
128+
129+
![step 17](https://user-images.githubusercontent.com/4365343/82228372-08311580-98f7-11ea-9faa-24050fc432d8.png)
130+
131+
### Using your new endpoint
132+
133+
You may now use your subdomain in place of your API load balancer endpoint in your client. For example, this curl request:
134+
135+
```bash
136+
curl http://a5044e34a352d44b0945adcd455c7fa3-32fa161d3e5bcbf9.elb.us-west-2.amazonaws.com/iris-classifier -X POST -H "Content-Type: application/json" -d @sample.json
137+
```
138+
139+
Would become:
140+
141+
```bash
142+
curl https://api.cortexlabs.dev/iris-classifier -X POST -H "Content-Type: application/json" -d @sample.json
143+
```
144+
145+
### Cleanup
146+
147+
Spin down your Cortex cluster.
148+
149+
Delete the hosted zone for your subdomain in the [Route 53 console](https://console.aws.amazon.com/route53/home#hosted-zones:):
150+
151+
![delete hosted zone](https://user-images.githubusercontent.com/4365343/82228729-81306d00-98f7-11ea-8570-e9de15f5267f.png)
152+
153+
Delete your certificate from the [ACM console](https://us-west-2.console.aws.amazon.com/acm/home):
154+
155+
![delete certificate](https://user-images.githubusercontent.com/4365343/82228835-a624e000-98f7-11ea-92e2-cb4fb0f591e2.png)

docs/miscellaneous/security.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,6 @@ In order to connect to the operator via the CLI, you must provide valid AWS cred
7272

7373
All APIs are accessible via HTTPS (in addition to HTTP). The SSL certificate is autogenerated during installation using `localhost` as the Common Name (CN). Therefore, clients will need to skip certificate verification (e.g. `curl -k`) when using HTTPS.
7474

75-
To use AWS's default (trusted) certificate or your own certificate, you can [set up API Gateway](../guides/api-gateway.md) to be a proxy to your Cortex cluster. Since the API load balancer created by Cortex is internet-facing by default, you will also need to set `api_load_balancer_scheme: internal` in your [cluster configuration](config.md) file (before creating your cluster) in order to force traffic to go through your API Gateway endpoint.
75+
To use AWS's default (trusted) certificate or your own certificate, you can [set up API Gateway](../guides/api-gateway.md) to be a proxy to your Cortex cluster. Since the API load balancer created by Cortex is internet-facing by default, in order to force traffic to go through your API Gateway endpoint, you will also need to set `api_load_balancer_scheme: internal` in your [cluster configuration](config.md) file (before creating your cluster).
76+
77+
Alternatively, you can create an SSL certificate for your custom domain, and use this certificate in the API load balancer (see our instructions for [setting up HTTPS on a subdomain](../guides/subdomain-https-setup.md)).

docs/summary.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
## Guides
4949

5050
* [Set up AWS API gateway](guides/api-gateway.md)
51+
* [Set up HTTPS on a subdomain](guides/subdomain-https-setup.md)
5152
* [Plot response code counts](guides/plot-response-code-counts.md)
5253
* [Plot API request time](guides/plot-request-time.md)
5354
* [Plot in-flight requests](guides/plot-in-flight-requests.md)

manager/install.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,11 @@ function setup_istio() {
288288
export CORTEX_OPERATOR_LOAD_BALANCER_ANNOTATION='service.beta.kubernetes.io/aws-load-balancer-internal: "true"'
289289
fi
290290

291+
export CORTEX_SSL_CERTIFICATE_ANNOTATION=""
292+
if [[ -n "$CORTEX_SSL_CERTIFICATE_ARN" ]]; then
293+
export CORTEX_SSL_CERTIFICATE_ANNOTATION="service.beta.kubernetes.io/aws-load-balancer-ssl-cert: $CORTEX_SSL_CERTIFICATE_ARN"
294+
fi
295+
291296
envsubst < manifests/istio-values.yaml | helm template istio-manifests/istio --values - --name istio --namespace istio-system | kubectl apply -f - >/dev/null
292297
}
293298

manager/manifests/istio-values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,16 @@ gateways:
8585
${CORTEX_API_LOAD_BALANCER_ANNOTATION}
8686
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
8787
service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: ${CORTEX_TAGS}
88+
${CORTEX_SSL_CERTIFICATE_ANNOTATION}
89+
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
8890
type: LoadBalancer
8991
externalTrafficPolicy: Local # https://medium.com/pablo-perez/k8s-externaltrafficpolicy-local-or-cluster-40b259a19404, https://www.asykim.com/blog/deep-dive-into-kubernetes-external-traffic-policies
9092
ports:
9193
- port: 80
9294
targetPort: 80
9395
name: http2
9496
- port: 443
97+
targetPort: 80
9598
name: https
9699
- port: 31400
97100
name: tcp

pkg/lib/aws/acm.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
Copyright 2020 Cortex Labs, Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package aws
18+
19+
import (
20+
"github.com/aws/aws-sdk-go/aws"
21+
"github.com/aws/aws-sdk-go/service/acm"
22+
"github.com/cortexlabs/cortex/pkg/lib/errors"
23+
)
24+
25+
func (c *Client) DoesCertificateExist(sslCertificateARN string) (bool, error) {
26+
_, err := c.ACM().DescribeCertificate(&acm.DescribeCertificateInput{
27+
CertificateArn: aws.String(sslCertificateARN),
28+
})
29+
30+
if err != nil {
31+
if IsErrCode(err, "ResourceNotFoundException") {
32+
return false, nil
33+
}
34+
return false, errors.Wrap(err, sslCertificateARN)
35+
}
36+
37+
return true, nil
38+
}

0 commit comments

Comments
 (0)