Skip to content

Commit

Permalink
Added support for aws_lambda_invocation (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
BSick7 authored Oct 18, 2023
1 parent 312d348 commit 67a8afc
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 6 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

This is a utility to administer postgres databases that are behind a firewall.

The published docker image runs with a lambda entrypoint.
Using a lambda that is on the same VPC as the database, this utility can ensure a database exists with a specific owner.
This utilizes AWS IAM to secure administration instead of using an SSH Tunnel or VPN.
This also limits the actions that a user can take, making it extremely hard to perform malicious commands.
Expand Down
20 changes: 15 additions & 5 deletions aws/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/nullstone-io/go-lambda-api-sdk/function_url"
"github.com/nullstone-modules/pg-db-admin/api"
"github.com/nullstone-modules/pg-db-admin/aws/secrets"
crud_invoke "github.com/nullstone-modules/pg-db-admin/crud-invoke"
"github.com/nullstone-modules/pg-db-admin/legacy"
"github.com/nullstone-modules/pg-db-admin/postgresql"
"github.com/nullstone-modules/pg-db-admin/setup"
Expand All @@ -27,11 +28,16 @@ func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

setupConnUrlSecretId := os.Getenv(dbSetupConnUrlSecretIdEnvVar)
log.Printf("Retrieving setup connection url secret (%s)\n", setupConnUrlSecretId)
dbSetupConnUrl, err := secrets.GetString(ctx, setupConnUrlSecretId)
if err != nil {
log.Println(err.Error())
var dbSetupConnUrl string
if setupConnUrlSecretId := os.Getenv(dbSetupConnUrlSecretIdEnvVar); setupConnUrlSecretId == "" {
log.Println("Skipping setup connection url secret")
} else {
log.Printf("Retrieving setup connection url secret (%s)\n", setupConnUrlSecretId)
var err error
dbSetupConnUrl, err = secrets.GetString(ctx, setupConnUrlSecretId)
if err != nil {
log.Println(err.Error())
}
}
adminConnUrlSecretId := os.Getenv(dbAdminConnUrlSecretIdEnvVar)
log.Printf("Retrieving admin connection url secret (%s)\n", adminConnUrlSecretId)
Expand All @@ -54,6 +60,10 @@ func HandleRequest(setupStore, adminStore *postgresql.Store) func(ctx context.Co
log.Println("Initial Setup Event")
return setup.Handle(ctx, event, setupStore, os.Getenv(dbAdminConnUrlSecretIdEnvVar))
}
if ok, event := crud_invoke.IsEvent(rawEvent); ok {
log.Println("Invocation (CRUD) Event", event.Tf.Action, event.Type)
return crud_invoke.Handle(ctx, event, adminStore)
}

if ok, event := isFunctionUrlEvent(rawEvent); ok {
router := api.CreateRouter(adminStore)
Expand Down
88 changes: 88 additions & 0 deletions crud-invoke/handle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package crud_invoke

import (
"context"
"encoding/json"
"fmt"
"github.com/nullstone-io/go-rest-api"
"github.com/nullstone-modules/pg-db-admin/postgresql"
)

// This package handles invocations from a Terraform `aws_lambda_invocation` CRUD resource
// When the resource has an attribute `lifecycle_scope = "CRUD"`,
// the payload will contain `tf` member with information about the action and previous input

type Event struct {
Type string `json:"type"`
Data json.RawMessage `json:"data"`
Tf EventTf `json:"tf"`
}

type EventTf struct {
Action string `json:"action"`
PrevInput any `json:"prev_input"`
}

func IsEvent(rawEvent json.RawMessage) (bool, Event) {
var event Event
if err := json.Unmarshal(rawEvent, &event); err != nil {
return false, event
}
return event.Tf.Action != "", event
}

func Handle(ctx context.Context, event Event, store *postgresql.Store) (any, error) {
crudHandler := CrudByName(store, event.Type)
if crudHandler == nil {
return nil, fmt.Errorf("unknown event 'type' %q", event.Type)
}

return crudHandler.Handle(event.Tf.Action, event.Data)
}

func CrudByName(s *postgresql.Store, name string) CrudHandler {
switch name {
case "databases":
return Crud[string, postgresql.Database]{DataAccess: s.Databases}
case "roles":
return Crud[string, postgresql.Role]{DataAccess: s.Roles}
case "role_members":
return Crud[postgresql.RoleMemberKey, postgresql.RoleMember]{DataAccess: s.RoleMembers}
case "schema_privileges":
return Crud[postgresql.SchemaPrivilegeKey, postgresql.SchemaPrivilege]{DataAccess: s.SchemaPrivileges}
case "default_grants":
return Crud[postgresql.DefaultGrantKey, postgresql.DefaultGrant]{DataAccess: s.DefaultGrants}
default:
return nil
}
}

type CrudHandler interface {
Handle(action string, raw json.RawMessage) (any, error)
}

type Keyer[TKey any] interface {
Key() TKey
}

type Crud[TKey any, T Keyer[TKey]] struct {
DataAccess rest.DataAccess[TKey, T]
}

func (h Crud[TKey, T]) Handle(action string, raw json.RawMessage) (any, error) {
var obj T
if err := json.Unmarshal(raw, &obj); err != nil {
return nil, fmt.Errorf("unable to parse input payload: %w", err)
}

switch action {
case "create":
return h.DataAccess.Create(obj)
case "update":
return h.DataAccess.Update(obj.Key(), obj)
case "delete":
return h.DataAccess.Drop(obj.Key())
default:
return nil, fmt.Errorf("unknown event 'action' %q", action)
}
}
4 changes: 4 additions & 0 deletions postgresql/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ type Database struct {
UseExisting bool `json:"useExisting"`
}

func (d Database) Key() string {
return d.Name
}

var _ rest.DataAccess[string, Database] = &Databases{}

type Databases struct {
Expand Down
4 changes: 4 additions & 0 deletions postgresql/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ type Role struct {
Attributes RoleAttributes `json:"attributes"`
}

func (r Role) Key() string {
return r.Name
}

type RoleAttributes struct {
CreateDb bool `json:"createDb"`
CreateRole bool `json:"createRole"`
Expand Down
7 changes: 7 additions & 0 deletions postgresql/role_member.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ type RoleMember struct {
UseExisting bool `json:"useExisting"`
}

func (r RoleMember) Key() RoleMemberKey {
return RoleMemberKey{
Member: r.Member,
Target: r.Target,
}
}

type RoleMemberKey struct {
Member string
Target string
Expand Down

0 comments on commit 67a8afc

Please sign in to comment.