├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── docdb_rest ├── __init__.py ├── app.py ├── auth.py └── requirements.txt └── template.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | *.toml 2 | *.zip 3 | .aws-sam/ 4 | resources/ 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Replace Python version with the one installed 2 | PYTHON=python3.9 3 | PACKAGES=pymongo 4 | 5 | all: layer-pymongo.zip 6 | 7 | clean: 8 | - rm -rf resources 9 | - rm -rf .aws-sam/* 10 | - rm layer-pymongo.zip 11 | 12 | layer-pymongo.zip: 13 | mkdir -p resources/python 14 | PYTHONUSERBASE=resources/python $(PYTHON) -m pip install --user $(PACKAGES) 15 | wget -O resources/python/global-bundle.pem https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem 16 | cd resources && zip -r ../layer-pymongo.zip python && cd .. 17 | rm -rf resources 18 | 19 | build: resources/layer-pymongo.zip template.yaml 20 | sam build 21 | 22 | sam: build 23 | sam deploy --capabilities CAPABILITY_NAMED_IAM --guided 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # REST API for Amazon DocumentDB 2 | 3 | ## Introduction 4 | This project will create a CRUD REST API for an existing 5 | Amazon DocumentDB cluster. It will use Amazon API Gateway 6 | and AWS Lambda functions to create the API, as well as 7 | use AWS Secrets Manager to manage credentials to the 8 | database. 9 | 10 | This project contains a SAM template and source 11 | code for the primary Lambda functions. 12 | 13 | ### Inputs 14 | The following are the inputs to the SAM template: 15 | - Prefix to prepend to resources 16 | - Amazon DocumentDB identifier 17 | - Username for Amazon DocumentDB (to be stored in Secrets Manager) 18 | - Password for Amazon DocumentDB (to be stored in Secrets Manager) 19 | - VPC Subnet for Amazon DocumentDB (for the AWS Lambda to be deployed) 20 | - Security group for access to Amazon DocumentDB (for the AWS Lambda) 21 | - Username to protect the API Gateway endpoints 22 | - Password to protect the API Gateway endpoints 23 | 24 | ## Build 25 | To package the zip file for the Lambda Layer run 26 | 27 | ``` 28 | make 29 | ``` 30 | 31 | or 32 | 33 | ``` 34 | make layer-pymongo.zip 35 | ``` 36 | 37 | To validate the SAM template run: 38 | 39 | ``` 40 | sam validate 41 | ``` 42 | 43 | To build the SAM template run: 44 | 45 | ``` 46 | sam build 47 | ``` 48 | 49 | or 50 | 51 | ``` 52 | make build 53 | ``` 54 | 55 | ## Deploy 56 | To deploy, run 57 | 58 | ``` 59 | sam deploy --guided 60 | ``` 61 | 62 | or 63 | 64 | ``` 65 | make sam 66 | ``` 67 | 68 | And follow the prompts. 69 | 70 | The REST API root endpoint will be recorded in the output variable: 71 | `APIRoot` 72 | 73 | ## Access 74 | The HTTP endpoint is protected by username/password access, which is set 75 | up using the supplied API username and API password. To access the you need 76 | to supply the username password in the request. 77 | 78 | The basic API is accessed at: `https://APIUSER:APIPASSWORD@APIROOT/{databaseName}/{collectionName}` 79 | 80 | For example, to access the collection `myColl` in the database `myDb` 81 | (and using the APIUSER as `apiuser` and APIPASSWORD as `apipassword`) 82 | you would use `https://apiuser:apipassword@APIROOT/myColl/myDb` 83 | 84 | This one endpoint will handle multiple REST calls, depending on the 85 | HTTP verb used. 86 | 87 | ### GET 88 | With the GET operation, you can issue a `find()` command. It takes the 89 | following arguments (all of which are optional): 90 | 91 | - `filter` : this is a filter clause in the MongoDB dialect. E.g., `filter={"a":1,"b":"two"}` 92 | - `projection` : this is a projection clause in the MongoDB dialect. E.g., `projection={"_id":0,"a":1}` 93 | - `sort` : this is a sort clause in the MongoDB dialect. E.g., `sort={"a":1,"b":-1}` 94 | - `limit` : this is an integer and the number of results to return. 95 | - `skip` : this is an integer and the number of results to skip before returning results. 96 | 97 | ### PUT and POST 98 | The PUT and POST verbs will perform the same operation, a database insert. The body 99 | of the HTTP request is the document to be stored in the database. 100 | 101 | ### PATCH 102 | The PATCH verb will perform a database update operation. The filter and update operations 103 | should be passed as a JSON document as the body of the request. The filter to use to identify 104 | which documents should be updated is passed as the filter field of the body. The update 105 | itself is passed as the update field in the body of the request and is in the MongoDB 106 | dialect. 107 | 108 | For example, to update all rows that have `a` equal to `100`, and increment the value of `b` 109 | by 10 and set the value of `c` to `three`, you would use the following as the body of the request: 110 | ``` 111 | { 112 | "filter": {"a":100}, 113 | "update": { 114 | "$inc": { 115 | "b": 10 116 | }, 117 | "$set": { 118 | "c": "three" 119 | } 120 | } 121 | } 122 | ``` 123 | 124 | ### DELETE 125 | The DELETE verb will perform a database delete operation. The filter should be passed as a 126 | JSON document as the body of the request. The filter to use to identify 127 | which documents should be deleted is passed as the filter field of the body. 128 | 129 | For example, to delete all documents with the value of `a` as 100, you would use 130 | ``` 131 | { 132 | "filter": {"a":100}, 133 | } 134 | ``` 135 | 136 | ## Examples 137 | For these examples, set the environment variables `APIUSER`, `APIPASSWORD`, 138 | and `APIROOT` to the API user, password, and root, respectively. For example: 139 | ``` 140 | export APIUSER=XXXXX 141 | export APIPASSWORD=YYYYY 142 | export APIROOT=ZZZZZ 143 | ``` 144 | 145 | 1. PUT some data 146 | ``` 147 | curl -X PUT -H "Content-Type: application/json" -d '{"name":"brian", "rating": 5}' https://$APIUSER:$APIPWD@$URLBASE/docdb/blog/test 148 | curl -X PUT -H "Content-Type: application/json" -d '{"name":"joe", "rating": 5}' https://$APIUSER:$APIPWD@$URLBASE/docdb/blog/test 149 | ``` 150 | 151 | 2. POST some data 152 | ``` 153 | curl -X POST -H "Content-Type: application/json" -d '{"name":"jason", "rating": 3}' https://$APIUSER:$APIPWD@$URLBASE/docdb/blog/test 154 | ``` 155 | 156 | 3. GET all the data 157 | ``` 158 | curl -G https://$APIUSER:$APIPWD@$URLBASE/docdb/blog/test 159 | ``` 160 | 161 | 4. GET the joe document 162 | ``` 163 | curl -G --data-urlencode 'filter={"name": "joe"}' https://$APIUSER:$APIPWD@$URLBASE/docdb/blog/test 164 | ``` 165 | 166 | 5. GET just the name field 167 | ``` 168 | curl -G --data-urlencode 'filter={"name": "joe"}' --data-urlencode 'projection={"_id": 0, "name": 1}' https://$APIUSER:$APIPWD@$URLBASE/docdb/blog/test 169 | ``` 170 | 171 | 6. PATCH the jason document 172 | ``` 173 | curl -X PATCH -H "Content-Type: application/json" -d '{"filter": {"name": "jason"},"update": {"$set": {"rating": 4}}}' https://$APIUSER:$APIPWD@$URLBASE/docdb/blog/test 174 | ``` 175 | 176 | 7. DELETE the jason document 177 | ``` 178 | curl -X DELETE -H "Content-Type: application/json" -d '{"filter": {"name": "jason"}}' https://$APIUSER:$APIPWD@$URLBASE/docdb/blog/test 179 | ``` 180 | 181 | ============================================== 182 | 183 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 184 | 185 | SPDX-License-Identifier: MIT-0 186 | 187 | -------------------------------------------------------------------------------- /docdb_rest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/docdb-rest/57f0a253d954f259d5a0e49377f601db0d932fb9/docdb_rest/__init__.py -------------------------------------------------------------------------------- /docdb_rest/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 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 | A copy of the License is located at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | or in the "license" file accompanying this file. This file is distributed 11 | on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | express or implied. See the License for the specific language governing 13 | permissions and limitations under the License. 14 | """ 15 | import json 16 | import boto3 17 | import os 18 | import ast 19 | import pymongo 20 | import bson 21 | import datetime 22 | ## GLOBALS 23 | db_client = None 24 | db_secret_name = os.environ['DB_SECRET_NAME'] 25 | pem_locator ='/opt/python/global-bundle.pem' 26 | datetime_str = "%Y-%m-%dT%H:%M:%S" 27 | ## HELPERS 28 | def stringify(doc): 29 | if (type(doc) == str): 30 | return doc 31 | if (type(doc) == dict): 32 | for k, v in doc.items(): 33 | doc[k] = stringify(v) 34 | if (type(doc) == list): 35 | for i in range(len(doc)): 36 | doc[i] = stringify(doc[i]) 37 | if (type(doc) == bson.ObjectId): 38 | doc = str(doc) 39 | if (type(doc) == datetime.datetime): 40 | doc = doc.strftime(datetime_str) 41 | return doc 42 | ## DOCUMENTDB CREDENTIALS 43 | def get_credentials(): 44 | """Retrieve credentials from the Secrets Manager service.""" 45 | boto_session = boto3.session.Session() 46 | try: 47 | secrets_client = boto_session.client(service_name='secretsmanager', region_name=boto_session.region_name) 48 | secret_value = secrets_client.get_secret_value(SecretId=db_secret_name) 49 | secret = secret_value['SecretString'] 50 | secret_json = json.loads(secret) 51 | username = secret_json['username'] 52 | password = secret_json['password'] 53 | host = secret_json['host'] 54 | port = secret_json['port'] 55 | return (username, password, host, port) 56 | except Exception as ex: 57 | raise 58 | ## DOCUMENTDB CONNECTION 59 | def get_db_client(): 60 | # Use a global variable so Lambda can reuse the persisted client on future invocations 61 | global db_client 62 | if db_client is None: 63 | try: 64 | # Retrieve Amazon DocumentDB credentials 65 | (secret_username, secret_password, docdb_host, docdb_port) = get_credentials() 66 | db_client = pymongo.MongoClient( 67 | host=docdb_host, 68 | port=docdb_port, 69 | tls=True, 70 | retryWrites=False, 71 | tlsCAFile=pem_locator, 72 | username=secret_username, 73 | password=secret_password, 74 | authSource='admin') 75 | except Exception as e: 76 | print('Failed to create new DocumentDB client: {}'.format(e)) 77 | raise 78 | return db_client 79 | ## Extract the db.collection from event and return collection 80 | def collection_from_event(event): 81 | path = event["path"].rstrip("/") 82 | print("path: " + path) 83 | splits = path.split("/") 84 | collection_name = splits[-1] 85 | db_name = splits[-2] 86 | get_db_client() 87 | print("db_name: " + db_name + "; collection_name: " + collection_name) 88 | db = db_client[db_name] 89 | collection = db[collection_name] 90 | return collection 91 | ## Handle DELETE 92 | def handle_delete(event): 93 | collection = collection_from_event(event) 94 | body = json.loads(event["body"]) 95 | filter = None 96 | if "filter" in body.keys(): 97 | filter = body["filter"] 98 | print(filter) 99 | res = collection.delete_many(filter) 100 | return stringify(res.raw_result) 101 | ## Handle PATCH 102 | def handle_patch(event): 103 | collection = collection_from_event(event) 104 | filter = None 105 | update = None 106 | body = json.loads(event["body"]) 107 | print(event["body"]) 108 | print(body) 109 | if "filter" in body.keys(): 110 | filter = body["filter"] 111 | if "update" in body.keys(): 112 | update = body["update"] 113 | print(filter) 114 | print(update) 115 | res = collection.update_many(filter, update) 116 | return stringify(res.raw_result) 117 | ## Handle POST 118 | def handle_post(event): 119 | collection = collection_from_event(event) 120 | body = json.loads(event["body"]) 121 | print(body) 122 | collection.insert_one(body) 123 | return stringify(body) 124 | ## Handle GET 125 | def handle_get(event): 126 | collection = collection_from_event(event) 127 | input_qs = event["queryStringParameters"] 128 | filter = None 129 | projection = None 130 | sort = None 131 | limit = 0 132 | skip = 0 133 | if None != input_qs: 134 | if "filter" in input_qs.keys(): 135 | filter = json.loads(input_qs["filter"]) 136 | if "projection" in input_qs.keys(): 137 | projection = json.loads(input_qs["projection"]) 138 | if "sort" in input_qs.keys(): 139 | sort = ast.literal_eval(input_qs["sort"]) 140 | if "limit" in input_qs.keys(): 141 | limit = int(input_qs["limit"]) 142 | if "skip" in input_qs.keys(): 143 | skip = int(input_qs["skip"]) 144 | 145 | print(filter) 146 | res = list(collection.find(filter=filter, projection=projection, sort=sort, limit=limit, skip=skip)) 147 | return stringify(res) 148 | 149 | # Lambda Handler 150 | def lambda_handler(event, context): 151 | print(event) 152 | httpMethod = event["httpMethod"] 153 | retval = "Done" 154 | if "GET" == httpMethod: 155 | retval = handle_get(event) 156 | elif "PUT" == httpMethod: 157 | retval = handle_post(event) 158 | elif "POST" == httpMethod: 159 | retval = handle_post(event) 160 | elif "PATCH" == httpMethod: 161 | retval = handle_patch(event) 162 | elif "DELETE" == httpMethod: 163 | retval = handle_delete(event) 164 | print(retval) 165 | 166 | return { 167 | 'statusCode': 200, 168 | 'body': json.dumps(retval) 169 | } 170 | -------------------------------------------------------------------------------- /docdb_rest/auth.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 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 | A copy of the License is located at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | or in the "license" file accompanying this file. This file is distributed 11 | on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | express or implied. See the License for the specific language governing 13 | permissions and limitations under the License. 14 | """ 15 | import os 16 | import json 17 | import base64 18 | 19 | def lambda_handler(event, context): 20 | print(event) 21 | action = "Allow" 22 | authorization = None 23 | if "Authorization" in event["headers"]: 24 | authorization = event["headers"]["Authorization"] 25 | if "authorization" in event["headers"]: 26 | authorization = event["headers"]["authorization"] 27 | if None == authorization: 28 | action = "Deny" 29 | splits = base64.b64decode(authorization.split(" ")[-1]).decode("utf-8").split(":") 30 | username = splits[0] 31 | password = splits[1] 32 | if (username != os.environ["USERNAME"] or password != os.environ["PASSWORD"]): 33 | print("Invalid username or password") 34 | action = "Deny" 35 | 36 | return buildPolicy(event, username, action) 37 | 38 | def buildPolicy(event, principalId, action): 39 | methodArn = event["methodArn"] 40 | splits = methodArn.split(":") 41 | awsRegion = splits[3] 42 | awsAccountId = splits[4] 43 | apisplits = splits[5].split("/") 44 | restApiId = apisplits[0] 45 | apiStage = apisplits[1] 46 | apiArn = "arn:aws:execute-api:" + awsRegion + ":" + awsAccountId + ":" + restApiId + "/" + apiStage + "/*/*" 47 | 48 | policy = { 49 | "principalId": principalId, 50 | "policyDocument": { 51 | "Version": "2012-10-17", 52 | "Statement": [ 53 | { 54 | "Action": "execute-api:Invoke", 55 | "Effect": action, 56 | "Resource": [apiArn] 57 | } 58 | ] 59 | } 60 | } 61 | print(policy) 62 | return policy 63 | -------------------------------------------------------------------------------- /docdb_rest/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/docdb-rest/57f0a253d954f259d5a0e49377f601db0d932fb9/docdb_rest/requirements.txt -------------------------------------------------------------------------------- /template.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Transform: AWS::Serverless-2016-10-31 4 | Description: This stack deploys a REST API for an Amazon DocumentDB cluster 5 | 6 | Parameters: 7 | 8 | Prefix: 9 | Type: String 10 | Description: Prefix for resources 11 | 12 | DocDBIdentifier: 13 | Type: String 14 | Description: Amazon DocumentDB cluster identifier 15 | 16 | DocDBUsername: 17 | Type: String 18 | Description: Username for the Amazon DocumentDB cluster 19 | 20 | DocDBPassword: 21 | Type: String 22 | Description: Password for the Amazon DocumentDB cluster 23 | NoEcho: true 24 | MinLength: 5 25 | 26 | DocDBVPCSubnet: 27 | Type: AWS::EC2::Subnet::Id 28 | Description: VPC Subnet with connectivity to Amazon DocumentDB cluster 29 | 30 | DocDBSecurityGroup: 31 | Type: AWS::EC2::SecurityGroup::Id 32 | Description: Security group with access to Amazon DocumentDB from within the VPC 33 | 34 | APIUsername: 35 | Type: String 36 | Description: Username to allow access to the API 37 | 38 | APIPassword: 39 | Type: String 40 | Description: Password to allow access to the API 41 | NoEcho: true 42 | MinLength: 4 43 | 44 | Resources: 45 | # Secret Manager 46 | DocDBSecret: 47 | Type: 'AWS::SecretsManager::Secret' 48 | Properties: 49 | Name: !Sub ${Prefix}-DocDBSecret 50 | Description: This secret has the credentials for the DocumentDB cluster 51 | SecretString: 52 | !Join 53 | - '' 54 | - - '{"username":"' 55 | - !Ref DocDBUsername 56 | - '","password":"' 57 | - !Ref DocDBPassword 58 | - '", "ssl": true}' 59 | 60 | SecretDocDBClusterAttachment: 61 | Type: AWS::SecretsManager::SecretTargetAttachment 62 | Properties: 63 | SecretId: !Ref DocDBSecret 64 | TargetId: !Ref DocDBIdentifier 65 | TargetType: AWS::DocDB::DBCluster 66 | 67 | # IAM 68 | LambdaExecutionRole: 69 | Type: AWS::IAM::Role 70 | Properties: 71 | AssumeRolePolicyDocument: 72 | Version: "2012-10-17" 73 | Statement: 74 | - 75 | Effect: Allow 76 | Principal: 77 | Service: 78 | - lambda.amazonaws.com 79 | Action: 80 | - sts:AssumeRole 81 | ManagedPolicyArns: 82 | - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole 83 | - arn:aws:iam::aws:policy/AWSLambdaExecute 84 | Policies: 85 | - PolicyName: DocumentDBSecret 86 | PolicyDocument: 87 | Version: '2012-10-17' 88 | Statement: 89 | - Effect: Allow 90 | Action: 91 | - secretsmanager:GetSecretValue 92 | Resource: !Ref DocDBSecret 93 | RoleName: !Sub ${Prefix}-Role-Lambda 94 | 95 | # Lambda 96 | LambdaLayerPymongo: 97 | Type: AWS::Serverless::LayerVersion 98 | Properties: 99 | LayerName: !Sub ${Prefix}-Layer-pymongo 100 | Description: Layer for Pymongo and RDS PEM file 101 | ContentUri: layer-pymongo.zip 102 | CompatibleRuntimes: 103 | - python3.9 104 | LicenseInfo: 'MIT' 105 | RetentionPolicy: Retain 106 | 107 | LambdaCRUDFunction: 108 | Type: AWS::Serverless::Function 109 | Properties: 110 | FunctionName: !Sub ${Prefix}-DocDBREST 111 | CodeUri: docdb_rest/ 112 | Handler: app.lambda_handler 113 | Layers: 114 | - !Ref LambdaLayerPymongo 115 | Runtime: python3.9 116 | Environment: 117 | Variables: 118 | DB_SECRET_NAME: !Sub ${Prefix}-DocDBSecret 119 | Role: !GetAtt LambdaExecutionRole.Arn 120 | VpcConfig: 121 | SecurityGroupIds: 122 | - !Ref DocDBSecurityGroup 123 | SubnetIds: 124 | - !Ref DocDBVPCSubnet 125 | Timeout: 5 126 | 127 | LambdaBasicAuthFunction: 128 | Type: AWS::Serverless::Function 129 | Properties: 130 | FunctionName: !Sub ${Prefix}-BasicAuth 131 | Handler: auth.lambda_handler 132 | Role: !GetAtt LambdaExecutionRole.Arn 133 | Runtime: python3.9 134 | Environment: 135 | Variables: 136 | USERNAME: !Ref APIUsername 137 | PASSWORD: !Ref APIPassword 138 | CodeUri: docdb_rest/ 139 | Timeout: 5 140 | 141 | LambdaPermissionAPICRUD: 142 | DependsOn: LambdaCRUDFunction 143 | Type: AWS::Lambda::Permission 144 | Properties: 145 | FunctionName: !GetAtt LambdaCRUDFunction.Arn 146 | Action: lambda:InvokeFunction 147 | Principal: apigateway.amazonaws.com 148 | SourceArn: 149 | !Join 150 | - '' 151 | - - 'arn:aws:execute-api:' 152 | - !Ref AWS::Region 153 | - ':' 154 | - !Ref AWS::AccountId 155 | - ':' 156 | - !Ref APIDocDBREST 157 | - '/*/*/*' 158 | 159 | LambdaPermissionAPIBasicAuth: 160 | DependsOn: LambdaBasicAuthFunction 161 | Type: AWS::Lambda::Permission 162 | Properties: 163 | FunctionName: !GetAtt LambdaBasicAuthFunction.Arn 164 | Action: lambda:InvokeFunction 165 | Principal: apigateway.amazonaws.com 166 | SourceArn: 167 | !Join 168 | - '' 169 | - - 'arn:aws:execute-api:' 170 | - !Ref AWS::Region 171 | - ':' 172 | - !Ref AWS::AccountId 173 | - ':' 174 | - !Ref APIDocDBREST 175 | - '/*/*' 176 | 177 | # API 178 | APIDocDBREST: 179 | Type: AWS::Serverless::Api 180 | Properties: 181 | StageName: !Sub ${Prefix}-api 182 | DefinitionBody: 183 | openapi: "3.0.1" 184 | info: 185 | title: "generic_docdb" 186 | paths: 187 | /docdb/{general_db}/{general_collection}: 188 | x-amazon-apigateway-any-method: 189 | parameters: 190 | - name: "general_db" 191 | in: "path" 192 | required: true 193 | schema: 194 | type: "string" 195 | - name: "general_collection" 196 | in: "path" 197 | required: true 198 | schema: 199 | type: "string" 200 | responses: 201 | "200": 202 | description: "200 response" 203 | content: 204 | application/json: 205 | schema: 206 | $ref: "#/components/schemas/Empty" 207 | security: 208 | - BasicAuthLambda: [] 209 | x-amazon-apigateway-integration: 210 | uri: 211 | !Join 212 | - '' 213 | - - 'arn:aws:apigateway:' 214 | - !Ref AWS::Region 215 | - ':lambda:path/2015-03-31/functions/' 216 | - !GetAtt LambdaCRUDFunction.Arn 217 | - '/invocations' 218 | responses: 219 | default: 220 | statusCode: "200" 221 | passthroughBehavior: "when_no_match" 222 | httpMethod: "POST" 223 | contentHandling: "CONVERT_TO_TEXT" 224 | type: "aws_proxy" 225 | /docdb/demodb/{general_collection}: 226 | x-amazon-apigateway-any-method: 227 | parameters: 228 | - name: "general_collection" 229 | in: "path" 230 | required: true 231 | schema: 232 | type: "string" 233 | responses: 234 | "200": 235 | description: "200 response" 236 | content: 237 | application/json: 238 | schema: 239 | $ref: "#/components/schemas/Empty" 240 | security: 241 | - BasicAuthLambda: [] 242 | x-amazon-apigateway-integration: 243 | uri: 244 | !Join 245 | - '' 246 | - - 'arn:aws:apigateway:' 247 | - !Ref AWS::Region 248 | - ':lambda:path/2015-03-31/functions/' 249 | - !GetAtt LambdaCRUDFunction.Arn 250 | - '/invocations' 251 | responses: 252 | default: 253 | statusCode: "200" 254 | passthroughBehavior: "when_no_match" 255 | httpMethod: "POST" 256 | contentHandling: "CONVERT_TO_TEXT" 257 | type: "aws_proxy" 258 | /docdb/demodb/democollection: 259 | x-amazon-apigateway-any-method: 260 | responses: 261 | "200": 262 | description: "200 response" 263 | content: 264 | application/json: 265 | schema: 266 | $ref: "#/components/schemas/Empty" 267 | security: 268 | - BasicAuthLambda: [] 269 | x-amazon-apigateway-integration: 270 | uri: 271 | !Join 272 | - '' 273 | - - 'arn:aws:apigateway:' 274 | - !Ref AWS::Region 275 | - ':lambda:path/2015-03-31/functions/' 276 | - !GetAtt LambdaCRUDFunction.Arn 277 | - '/invocations' 278 | responses: 279 | default: 280 | statusCode: "200" 281 | passthroughBehavior: "when_no_match" 282 | httpMethod: "POST" 283 | contentHandling: "CONVERT_TO_TEXT" 284 | type: "aws_proxy" 285 | components: 286 | schemas: 287 | Empty: 288 | title: "Empty Schema" 289 | type: "object" 290 | securitySchemes: 291 | BasicAuthLambda: 292 | type: "apiKey" 293 | name: "Authorization" 294 | in: "header" 295 | x-amazon-apigateway-authtype: "custom" 296 | x-amazon-apigateway-authorizer: 297 | authorizerUri: 298 | !Join 299 | - '' 300 | - - 'arn:aws:apigateway:' 301 | - !Ref AWS::Region 302 | - ':lambda:path/2015-03-31/functions/' 303 | - !GetAtt LambdaBasicAuthFunction.Arn 304 | - '/invocations' 305 | authorizerResultTtlInSeconds: 300 306 | identitySource: "method.request.header.Authorization" 307 | type: "request" 308 | x-amazon-apigateway-gateway-responses: 309 | UNAUTHORIZED: 310 | statusCode: 401 311 | responseParameters: 312 | gatewayresponse.header.WWW-Authenticate: "'Basic'" 313 | responseTemplates: 314 | application/json: "{\"message\":$context.error.messageString}" 315 | 316 | 317 | 318 | Outputs: 319 | StackName: 320 | Value: !Sub ${AWS::StackName} 321 | DocDBSecret: 322 | Value: !Ref DocDBSecret 323 | LambdaExecutionRole: 324 | Value: !Ref LambdaExecutionRole 325 | LambdaLayerPymongo: 326 | Value: !Ref LambdaLayerPymongo 327 | LambdaCRUDFunction: 328 | Value: !Ref LambdaCRUDFunction 329 | LambdaBasicAuthFunction: 330 | Value: !Ref LambdaBasicAuthFunction 331 | APIDocDBREST: 332 | Value: !Ref APIDocDBREST 333 | APIRoot: 334 | Value: !Sub ${APIDocDBREST}.execute-api.${AWS::Region}.amazonaws.com/${Prefix}-api/docdb 335 | --------------------------------------------------------------------------------