Skip to content
Open
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
10 changes: 6 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,18 @@ venv: requirements.txt
virtualenv -p python3 venv && \
. ./venv/bin/activate && \
pip install --quiet --upgrade pip && \
pip install --quiet -r requirements.txt
pip install --quiet -r requirements.txt

clean:
rm -rf venv target
rm -rf src/*.pyc tests/*.pyc
rm -rf src/*.pyc tests/*.pyc src/__pycache__ tests/__pycache__

test: venv
test-templates:
for i in $$PWD/cloudformation/*; do \
aws cloudformation validate-template --template-body file://$$i > /dev/null || exit 1; \
done

test: venv
. ./venv/bin/activate && \
pip install --quiet -r requirements.txt -r test-requirements.txt && \
cd src && \
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@ It is quite easy: you specify a CloudFormation resource of the [Custom::PostgreS
ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:binxio-cfn-secret-provider'
```

After the deployment, the Postgres user 'kong' has been created together with a matching database 'kong'. The password for the root database user has been obtained by querying the Parameter `/postgres/root/PGPASSWORD`. If you just want to create a user with which you can login to the PostgreSQL database server, without a database, specify `WithDatabase` as `false`. If `WithPublicSchema` is set to false, permission to create in the schema `public` is revoked.
After the deployment, the Postgres user 'kong' has been created together with a matching database 'kong'.
The password for the root database user has been obtained by querying the Parameter `/postgres/root/PGPASSWORD`.
If you just want to create a user with which you can login to the PostgreSQL database server, without a database, specify `WithDatabase` as `false`.
If `WithPublicSchema` is set to false, permission to create in the schema `public` is revoked.

The RetainPolicy by default is `Retain`. This means that the login to the database is disabled. If you specify drop, it will be dropped and your data will be lost.
The RetainPolicy by default is `Drop`.
This means the schema will be dropped and your data will be lost when deleting this resource in CloudFormation.


## Installation
Expand Down
6 changes: 6 additions & 0 deletions docker-compose.override.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: '2.4'

services:
aurora:
ports:
- "5432:5432"
6 changes: 6 additions & 0 deletions docker-compose.test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: '2.4'

services:
aurora:
ports:
- "5432"
16 changes: 16 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: "2.4"

services:
aurora:
image: 280925583500.dkr.ecr.eu-central-1.amazonaws.com/postgres:13
ports:
- "5432"
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
healthcheck:
test: ["CMD", "psql", "-t", "-d", "postgres", "-U", "postgres", "-c", "select usename from pg_catalog.pg_user"]
interval: 10s
timeout: 5s
retries: 5
40 changes: 40 additions & 0 deletions docs/PostgreSQLExtension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Custom::PostgreSQLExtension
The `Custom::PostgresSQLExtension` resource creates a postgres Extension in the mentioned database.


## Syntax
To declare this entity in your AWS CloudFormation template, use the following syntax:

```yaml
Type: Custom::PostgreSQLExtension
Properties:
Extension: STRING
DeletionPolicy: STRING
Database:
Host: STRING
Port: INTEGER
Database: STRING
User: STRING
Password: STRING
PasswordParameterName: STRING
ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:binxio-cfn-dbuser-provider-vpc-${AppVPC}'
```

## Properties
You can specify the following properties:

- `Extension` - the name of a valid postgresql database extensions
- `DeletionPolicy` - when the resource is deleted. Default: `Drop`
- `Database` - connection information of the database owner
- `Host` - the database server is listening on.
- `Port` - port the database server is listening on.
- `Database` - name of the database to connect to.
- `User` - name of the database owner.
- `Password` - to identify the user with.
- `PasswordParameterName` - name of the parameter in the store containing the password of the user

Either `Password` or `PasswordParameterName` is required.

## Return values
There are no return values from this resources.

40 changes: 40 additions & 0 deletions docs/PostgreSQLRoleGrant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Custom::PostgreSQLRoleGrant
The `Custom::PostgresSQLRoleGrant` resource links a role to a role or user.


## Syntax
To declare this entity in your AWS CloudFormation template, use the following syntax:

```yaml
Type: Custom::PostgreSQLRoleGrant
Properties:
Grantee: STRING
Role: STRING
Database:
Host: STRING
Port: INTEGER
Database: STRING
User: STRING
Password: STRING
PasswordParameterName: STRING
ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:binxio-cfn-dbuser-provider-vpc-${AppVPC}'
```

## Properties
You can specify the following properties:

- `Grantee` - user or role to grant to
- `Role` - role to grant to user or role
- `Database` - connection information of the database owner
- `Host` - the database server is listening on.
- `Port` - port the database server is listening on.
- `Database` - name of the database to connect to.
- `User` - name of the database owner.
- `Password` - to identify the user with.
- `PasswordParameterName` - name of the parameter in the store containing the password of the user

Either `Password` or `PasswordParameterName` is required.

## Return values
There are no return values from this resources.

4 changes: 2 additions & 2 deletions docs/PostgreSQLSchema.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ You can specify the following properties:

- `Schema` - to create
- `Owner` - of the schema
- `DeletionPolicy` - when the resource is deleted
- `DeletionPolicy` - when the resource is deleted. Default: `Drop`
- `Database` - connection information of the database owner
- `Host` - the database server is listening on.
- `Port` - port the database server is listening on.
- `Database` - name to connect to.
- `Database` - name of the database to connect to.
- `User` - name of the database owner.
- `Password` - to identify the user with.
- `PasswordParameterName` - name of the parameter in the store containing the password of the user
Expand Down
4 changes: 2 additions & 2 deletions docs/PostgreSQLUser.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ You can specify the following properties:
- `Password` - of the user
- `PasswordParameterName` - name of the parameter in the store containing the password of the user
- `WithDatabase` - if a database is to be created with the same name, defaults to true
- `DeletionPolicy` - when the resource is deleted
- `DeletionPolicy` - when the resource is deleted. Default: `Drop`
- `Database` - connection information of the database owner
- `Host` - the database server is listening on.
- `Port` - port the database server is listening on.
- `Database` - name to connect to.
- `Database` - name of the database to connect to.
- `User` - name of the database owner.
- `Password` - to identify the user with.
- `PasswordParameterName` - name of the parameter in the store containing the password of the user
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ psycopg2-binary
requests
jsonschema
cfn_resource_provider>=1.0.6
AsIs
4 changes: 4 additions & 0 deletions src/postgresql.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os
import logging

import postgresql_extension_provider
import postgresql_schema_provider
import postgresql_role_grant_provider
import postgresql_user_provider
Expand All @@ -11,6 +13,8 @@ def handler(request, context):
return postgresql_schema_provider.handler(request, context)
elif request['ResourceType'] == 'Custom::PostgreSQLRoleGrant':
return postgresql_role_grant_provider.handler(request, context)
elif request['ResourceType'] == 'Custom::PostgreSQLExtension':
return postgresql_extension_provider.handler(request, context)

else:
return postgresql_user_provider.handler(request, context)
Expand Down
155 changes: 155 additions & 0 deletions src/postgresql_extension_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import logging
import os

from postgresql_user_provider import PostgreSQLUser
from psycopg2.extensions import AsIs

log = logging.getLogger()

log.setLevel(os.environ.get("LOG_LEVEL", "INFO"))

request_schema = {
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"oneOf": [{"required": ["Database", "Extension"]}],
"properties": {
"Database": {"$ref": "#/definitions/connection"},
"Extension": {
"type": "string",
"pattern": "^[A-Za-z0-9_]*$",
"description": "postgres extension",
},
"DeletionPolicy": {
"type": "string",
"default": "Drop",
"enum": ["Drop", "Retain"],
},
},
"definitions": {
"connection": {
"type": "object",
"oneOf": [
{"required": ["DBName", "Host", "Port", "User", "Password"]},
{
"required": [
"DBName",
"Host",
"Port",
"User",
"PasswordParameterName",
]
},
],
"properties": {
"DBName": {"type": "string", "description": "the name of the database"},
"Host": {"type": "string", "description": "the host of the database"},
"Port": {
"type": "integer",
"default": 5432,
"description": "the network port of the database",
},
"User": {
"type": "string",
"description": "the username of the database owner",
},
"Password": {
"type": "string",
"description": "the password of the database owner",
},
"PasswordParameterName": {
"type": "string",
"description": "the name of the database owner password in the Parameter Store.",
},
},
}
},
}


class PostgreSQLExtension(PostgreSQLUser):
def __init__(self):
super(PostgreSQLExtension, self).__init__()
self.request_schema = request_schema

def is_supported_resource_type(self):
return self.resource_type == "Custom::PostgreSQLExtension"

@property
def database(self):
return self.dbname

@property
def extension(self):
return self.get("Extension")

@property
def old_extension(self):
return self.get_old("Extension", self.extension)

@property
def extension(self):
return self.get("Extension")

@property
def deletion_policy(self):
return self.get("DeletionPolicy")

def create_extension(self):
log.info("creating extension %s for database %s ", self.extension, self.database)
with self.connection.cursor() as cursor:
cursor.execute("Create Extension if not exists %s", [AsIs(self.extension)])

def update_extension(self):
if self.extension != self.old_extension:
log.info("removing old extension %s and implementing %s on database %s", self.old_extension, self.extension, self.database)
log.info("drop extension %s ", self.extension)
with self.connection.cursor() as cursor:
cursor.execute("DROP EXTENSION IF EXISTS %s CASCADE", [AsIs(self.old_extension)])
log.info("creating extension %s on database %s ", self.extension, self.database)
with self.connection.cursor() as cursor:
cursor.execute("Create Extension if not exists %s", [AsIs(self.extension)])

def drop_extension(self):
log.info("drop extension %s ", self.extension)
with self.connection.cursor() as cursor:
cursor.execute("DROP EXTENSION IF EXISTS %s CASCADE", [AsIs(self.extension)])

def create(self):
try:
self.connect()
self.create_extension()
self.physical_resource_id = self.logical_resource_id
except Exception as e:
self.physical_resource_id = "could-not-create"
self.fail("Failed to create extension, %s" % e)
finally:
self.close()

def update(self):
try:
self.connect()
self.update_extension()
except Exception as e:
self.fail("Failed to update the extension, %s" % e)
finally:
self.close()

def delete(self):
if self.physical_resource_id == "could-not-create" or self.deletion_policy == "Retain":
self.success("extension was never created")
return

try:
self.connect()
self.drop_extension()
except Exception as e:
return self.fail("Failed to drop extension %s" % e)
finally:
self.close()


provider = PostgreSQLExtension()


def handler(request, context):
return provider.handle(request, context)
2 changes: 1 addition & 1 deletion src/postgresql_role_grant_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def update(self):
self.close()

def delete(self):
if self.physical_resource_id == "could-not-create":
if self.physical_resource_id == "could-not-create" or self.deletion_policy == "retain":
self.success("role was never granted")
return

Expand Down
Loading