I have used HTTPS for AWS Application Load Balancer listener by using ACM certificate.
I requested the public certificate for a subdomain from ACM: test.example.com and create a CNAME in route 53 for it:
Name: _xxxxxxxxxxx.test.example.com
Type: CNAME
Value: xxxxxx.xxx.acm-validations.aws.
I can use the DNS of ALB (xxxx.us-east-1.elb.amazonaws.com) to call the API successfully in POSTMAN, however, when I use python requests or cURL to call the same API, it will always tell me there is some issue with the SSL.
cURL:
Code:
curl -X POST \
https://xxxxx.us-east-1.elb.amazonaws.com/prod/testapi \
-H 'Content-Type: application/json' \
-H 'cache-control: no-cache' \
-d '{
"paras1": "xxxxx"
}'
Error:
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
* subject: CN=test.example.com
* start date: Nov 11 00:00:00 2018 GMT
* expire date: Dec 11 12:00:00 2019 GMT
* subjectAltName does not match xxxx.us-east-1.elb.amazonaws.com
* SSL: no alternative certificate subject name matches target host name 'xxxx.us-east-1.elb.amazonaws.com'
* Closing connection 0
* TLSv1.2 (OUT), TLS alert, Client hello (1):
curl: (51) SSL: no alternative certificate subject name matches target host name 'xxxx.us-east-1.elb.amazonaws.com'
Python Requests:
Code:
import requests
url = "https://xxxxx.us-east-1.elb.amazonaws.com/prod/testapi"
payload = "{\"paras1\": \"xxxxx\"}"
headers = {
'Content-Type': "application/json",
'cache-control': "no-cache"
}
response = requests.request("POST", url, data=payload, headers=headers)
print(response.text)
Error:
HTTPSConnectionPool(host='xxxx.us-east-1.elb.amazonaws.com', port=443):
Max retries exceeded with url: /prod/testapi
(Caused by SSLError(CertificateError("hostname 'xxxx.us-east-1.elb.amazonaws.com' doesn't match 'test.example.com'",),))
I can use the DNS of ALB (xxxx.us-east-1.elb.amazonaws.com)
That isn't how this is designed to work. You need to point test.example.com to the ELB in DNS, and then:
url = "https://test.example.com/prod/testapi"
Clearly, you call xxxx.us-east-1.elb.amazonaws.com which is set up with a certificate for test.example.com. Despite the certificate may be valid it does not match the URL you are calling which means that the certificate is NOT valid for THIS call. I think you also must set a custom domain for the API gateway. https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html
Edit: thanks for the comment. I am not sure how I read "elb" as an api gateway. My fault. Still the DNS for the ELB has to match the one in the certificat. You can create a CNAME from your domain to the ELB domain. This should work (at least this is how we do that).
Related
I'm trying to use the token provided by AWS Cognito to access a URL via Postman or cURL, but I'm failing to.
I have used the CloudFormation template bellow to create an API with a JWT authentication.
https://github.com/awsdocs/amazon-api-gateway-developer-guide/blob/main/cloudformation-templates/HTTP/http-with-jwt-auth.yaml
After signing-in, I can access the lambda function using the returned URL and access_token. This works just as expected:
http://<api_url>/?access_token=<token>
But when I try to access it from Postman or cURL using the access_token in the header, it outputs a 401. I was expecting to have access granted.
$ curl -v -X GET <url> -H "Authorization: <token>"
{"message":"Unauthorized"}
What have I tried:
I have tried to add 'Content-Type: application/json', but still get 401.
I have tried to use Authorization: Bearer <token>, but still get 401.
This template only return the access_token, but another stack I have also returns the id_token, and a 401 is returned for both
The
complete returned header is:
HTTP/2 401
date: Thu, 03 Mar 2022 20:12:58 GMT
content-type: application/json
content-length: 26
www-authenticate: Bearer
apigw-requestid: ObIjqhmPIAMEJtA=
* Connection #0 to host <url> left intact
{"message":"Unauthorized"}
The JWT Authorizer is configured as:
JWTAuthorizer:
Type: AWS::ApiGatewayV2::Authorizer
Properties:
ApiId: !Ref MyAPI
AuthorizerType: JWT
IdentitySource:
- '$request.querystring.access_token'
JwtConfiguration:
Audience:
- !Ref AppClient
Issuer: !Sub https://cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}
Name: test-jwt-authorizer
The IdentitySource must be '$request.header.Authorization' in order for it to read from header.Authorization.
With the config below the issued certificate works for www.example.org and abc.example.org.but doesn't work for www.abc.example.org.
http:
routers:
web-router:
rule: "Host(`example.org`) ||
Host(`www.example.org`) ||
HostRegexp(`{subdomain:[a-z-]+}.example.org`) ||
HostRegexp(`www.{subdomain:[a-z-]+}.example.org`)"
entryPoints:
- web
middlewares:
- redirect
- csrf
service: django
web-secure-router:
rule: "Host(`example.org`) ||
Host(`www.example.org`) ||
HostRegexp(`{subdomain:[a-z-]+}.example.org`) ||
HostRegexp(`www.{subdomain:[a-z-]+}.example.org`)"
entryPoints:
- web-secure
middlewares:
- csrf
service: django
tls:
certResolver: letsencrypt
domains:
- main: "*.example.org"
sans:
- "example.org"
certificatesResolvers:
letsencrypt:
acme:
email: "email#example.com"
storage: /etc/traefik/acme/acme.json
dnsChallenge:
provider: route53
delayBeforeCheck: 0
In the DNS I have A records for example.org and *.example.org.
Is it possible to get a certificate that works for www.*.example.org and *.example.org in the same time?
LetsEncrypt doesn't do nested wildcards. You can get a certificate for *.example.org or for *.sub.example.org, but *.example.org doesn't include . in the definition of *.
Furthermore, DNS itself doesn't support nested or embedded wildcards.
You can see this if you create a record for www.*.example.com and then query for something like www.foo.example.com. You'll get an NXDOMAIN response. If you query for www.\*.example.com (or wrap the FQDN in single quotes to avoid shell expansion), then you'll get the A record you created. The asterisk becomes a literal character when not at the beginning of the record.
➤ host www.sub.productionwebsite.com 8.8.8.8
Using domain server:
Name: 8.8.8.8
Address: 8.8.8.8#53
Aliases:
Host www.sub.productionwebsite.com not found: 3(NXDOMAIN)
~
➤ host 'www.*.productionwebsite.com' 8.8.8.8
Using domain server:
Name: 8.8.8.8
Address: 8.8.8.8#53
Aliases:
www.*.productionwebsite.com has address 10.68.0.73
I have a single page application and I'm trying to prevent clickjacking by adding X-Frame-Options header to the HTML responses. My website is hosted on S3 through CloudFront.
The CloudFront distribution is configured to send index.html by default:
Default root object
index.html
In the Error Pages section I configured 404 page to also point to the index.html. This way all URLs that are not in S3 return the default HTML, i.e. /login, /admin etc.
Update The 403 status code is also configured:
Then I have created a CloudFront function as described here and assigned it to the Viewer response:
function handler(event) {
var response = event.response;
var headers = response.headers;
headers['x-frame-options'] = {value: 'DENY'};
return response;
}
This works, but only for /:
curl -v https://<MYSITE.com>
....
< x-frame-options: DENY
For other URLs it doesn't work - the x-frame-options header is missing:
curl -v https://<MYSITE.com>/login
....
< x-cache: Error from cloudfront
My question is - why my cloudfront function does not append a header in the error response, and what can I do to add it?
I understand that your questions are:
Q1: Why does the CloudFront function work for /?
Q2: Why doesn't the CloudFront function work for other url path?
Please refer to the responses below:
A1: Since you might specify a Default Root Object [1] (e.g.index.html) which returning the object when a user requests the root URL. When CloudFront returns the object with 200 ok, the CloudFront Function will be invoked on the viewer response event.
A2: You might not give the s3:ListBucket permissions in your S3 bucket policy(e.g. OAI). As the result, you will get Access Denied(403) errors for missing objects instead of 404 Not Found errors. Namely, the Error Pages you have configured isn't applied to this case, and the CloudFront Function won't be invoked because the HTTP status code is higher than 399[2].
[Updated] Suggestion:
Since CloudFront does not invoke edge functions for viewer response events when the origin returns HTTP status code 400 or higher. However, Lambda#Edge functions for origin response events are invoked for all origin responses. In this senario, I'll suggest that we should use Lambda#Edge instead of CloudFront Functions.
For your convenience, please refer to the sample code of l#e:
exports.handler = async (event, context) => {
const response = event.Records[0].cf.response;
const headers = response.headers;
headers['x-frame-options'] = [{
key: 'X-Frame-Options',
value: 'DENY',
}];
return response;
};
FYI. Here is my curl test result:
# PATH: `/`
$ curl -sSL -D - https://dxxxxxxx.cloudfront.net/
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 12
Connection: keep-alive
ETag: "e59ff97941044f85df5297e1c302d260"
___snipped___
Server: AmazonS3
X-Frame-Options: DENY
___snipped___
# PATH: `/login`
$ curl -sSL -D - https://dxxxxxxx.cloudfront.net/login
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 12
Connection: keep-alive
ETag: "e59ff97941044f85df5297e1c302d260"
___snipped___
Server: AmazonS3
X-Frame-Options: DENY
___snipped___
I have been trying to enable CORS headers on Google app engine but none of the methods that I found over the internet worked for me.
My application is on Python/Django and I want my frontend application (which is hosted separately) to be able to make API calls to my backend platform on Google App Engine.
The January 2017 release notes say that
We are changing the behavior of the Extensible Service Proxy (ESP) to deny cross-origin resource sharing (CORS) requests by default
It can be seenhere
And the solution to enable CORS given by them is to add the following snippet to the service's OpenAPI configuration.
"host": "echo-api.endpoints.YOUR_PROJECT_ID.cloud.goog",
"x-google-endpoints": [
{
"name": "echo-api.endpoints.YOUR_PROJECT_ID.cloud.goog",
"allowCors": "true"
}
],
...
So I followed this example and created two files in my code base
openapi.yml :
swagger: "2.0"
info:
description: "Google Cloud Endpoints APIs"
title: "APIs"
version: "1.0.0"
host: "echo-api.endpoints.<PROJECT-ID>.cloud.goog"
x-google-endpoints:
- name: "echo-api.endpoints.<PROJECT-ID>.cloud.goog"
allowCors: "true"
paths:
"/api/v1/sign-up":
post:
description: "Sends an email for verfication"
operationId: "signup"
produces:
- "application/json"
responses:
200:
description: "OK"
parameters:
- description: "Email address of the user"
in: body
name: email
required: true
schema:
type: string
- description: "password1"
in: body
name: password1
required: true
schema:
type: string
- description: "password2"
in: body
name: password2
required: true
schema:
type: string
openapi-appengine.yml:
swagger: "2.0"
info:
description: "Google Cloud Endpoints API fo localinsights backend server"
title: "Localinsights APIs"
version: "1.0.0"
host: "<PROJECT-ID>.appspot.com"
Then I ran this command:
gcloud service-management deploy openapi.yml
Then I edited my app.yml file to make it look like this (The addition was endpoints_api_service. Before adding this, the app was getting deployed without any errors):
runtime: python
env: flex
entrypoint: gunicorn -b :$PORT myapp.wsgi
beta_settings:
cloud_sql_instances: <cloud instance>
runtime_config:
python_version: 3
automatic_scaling:
min_num_instances: 1
max_num_instances: 1
resources:
cpu: 1
memory_gb: 0.90
disk_size_gb: 10
env_variables:
DJANGO_SETTINGS_MODULE: myapp.settings.staging
DATABASE_URL: <dj-database-url>
endpoints_api_service:
name: "<PROJECT-ID>.appspot.com"
config_id: "<CONFIG-ID>"
Then I just deployed the application with
gcloud app deploy
Now, the app got deployed successfully but it is behaving strangely. All the requests which are supposed to return a 200 response still throw CORS error but the ones which return a 400 status do work.
For example - The sign up API expects these fields - email, password1, password2 where password1 should be same as password2. Now when I send correct parameters, I get HTTP 502 saying
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin {origin-url} is therefore not allowed access. The response had HTTP status code 502
But when I send password1 not same as password2, I get HTTP 400 response which I am sure is coming from my code because the response is a dictionary written in the code if password1 and password2 do not match. Also in this case, the headers have Access-Control-Allow-Origin as * but in the former case, that was not true
I also checked my nginx error logs and it says
*27462 upstream prematurely closed connection while reading response header
What am I doing wrong here?
Is this the right way to enable CORS in GAE?
After banging my head for several days, I was able to figure out the the real problem. My database server was denying any connection to the webapp server.
Since in case of a HTTP 200 response, the webapp is supposed to make a database call, the webapp was trying to connect to the database server. This connection was taking too long and as soon as it reached beyond the NGINX's timeout time, NGINX used to send a response to the web browser with the status code as 502.
Since the 'access-control-allow-origin' header was being set from the webapp, NGINX did not set that header in its response. Hence the browser was interpreting it as a CORS denial.
As soon as I whitelisted my webapp's instance's IP address for the database server, things started running smoothly
Summary:
There is no need of openapi.yml file to enable CORS for a Django application on GAE flexible environment
Do not miss to check the NGINX logs :p
Update:
Just wanted to update my answer to specify the way through which you won't have to add you instance's IP to the whitelisted IP(s) of the SQL instance
Configure the DATABASES like this:
DATABASES = {
'HOST': <your-cloudsql-connection-string>, # This is the tricky part
'ENGINE': <db-backend>,
'NAME': <db-name>,
'USER': <user>,
'PASSWORD': <password>
}
Note the HOST key in the databases. GAE has a way through which you won't have to whitelist your instance's IP but for that to work, the host should be the cloudsql-connection-string and NOT the IP of the SQL instance.
If you are not sure what's your cloudsql-connection-string, go to the Google cloud platform dashboard and select the SQL tab under the Storage section. You should see a table with a column Instance connection name. The value under this column is your cloudsql-connection-string.
Nginx as your reverse proxy, so, as the gateway to your server, should be who manage CORS against client browser requests, as first contact from beyond to your system. Should not be any of the backend servers (neither your database, neither anything).
Here you got my default config to enable CORS in nginx from Ajax calls to a REST service of my own (backserver glassfish). Feel free to check and use it and hope it serves to you.
server {
listen 80; ## listen for ipv4; this line is default and implied
server_name codevault;
#Glassfish
location /GameFactoryService/ {
index index.html;
add_header Access-Control-Allow-Origin $http_origin;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-NginX-Proxy true;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:18000/GameFactoryService/;
}
#static content
location / {
root /usr/share/nginx_static_content;
}
error_page 500 501 502 503 504 505 506 507 508 509 510 511 /50x.html;
#error
location = /50x.html {
add_header Access-Control-Allow-Origin $http_origin;
internal;
}
}
How do you redirect a www subdomain to an apex (naked) domain using Route53 and CloudFront?
Please note I am not using S3.
My site is hosted behind an ELB on the AWS ECS service.
I've tried to create a Record Set with the following settings:
- Name: www
- Type: CNAME - Canonical name
- Alias: No
- TTL (Seconds): 300
- Value: example.com
In CloudFront I have only the example.com apex whitelisted. As a result, the request is blocked.
I've also tried adding the www.example.com entry in CloudFront and the site resolves, however, the browser is not redirected example.com
AWS have introduced CloudFront functions which can manipulate the requests and responses that flow through CloudFront.
To redirect a www subdomain to the apex domain you can use the following viewer request function:
function handler(event) {
var request = event.request;
var headers = request.headers;
var host = request.headers.host.value;
if (host.startsWith('www')) {
var response = {
statusCode: 301,
statusDescription: 'Moved Permanently',
headers: {
location: {
value: 'https://' + host.replace('www.', ''),
},
},
};
return response;
}
return request;
}
In order for this function to work the CloudFront distribution needs to be configured with both the www subdomain and the apex domain. You will also need to configure Route 53 with both the subdomain and apex domain.
Using S3 for the redirect is not possible for domains (such as .dev) where the entire gTLD has been included in the HSTS preload-list because S3 website do not support https.
If you are considering to redirect using CloudFront and Route53, currently you have to write an Edge Lambda for HTTP Response Generation to Redirect.