Sym solves painful access and approval problems with practical workflows-as-code.

Is your team experiencing:

Sym can help! This quickstart will help you launch a new access flow in hours. Your engineers will be able to safely and conveniently gain access to a Postgres database, all with the guardrails you need in place.

If you want to check out a demo, go here!

Workflow: Postgres Access

We're going to walk through setting up an access control workflow using Slack, AWS Lambda and Sym. By the end of this tutorial, you'll have the ability to wrap access to a Postgres database with a fully-configurable request-and-approval flow, using a declaratively provisioned Slack bot.

The complete code for this tutorial can be found at @symopsio/sym-postgres-quickstart.

Users will interact with this Sym Flow via Slack. Slack connects to the Sym platform, which executes a Flow that use the Integrations we are wiring together in this tutorial.

End-User Workflow

Making Requests

This is what a request will look like.

Request Modal

Sym will send a request for approval to the appropriate users or channel based on your

Approval Request

Finally, upon approval, Sym invokes your AWS Lambda function and updates Slack.

Approved Access

Our starter Lambda implementation assigns the requesting user to a configurable Postgres role. You can tune the implementation to match the specific needs of your database setup.

To complete this tutorial, you should install Terraform, and make sure you have a working install of Python 3.

What's Next

The environment includes almost everything you need to get the Postgres workflow up and running. You'll have to tune the Postgres Lambda implementation to fit your exact workflow, but our starter implementation should help a lot with this. Then just configure a few variables in terraform.tfvars and you're on your way!

Here's what you'll need to do:

You'll need to build the Lambda layer and handler zips so that Terraform can complete provisioning. The default implementation grants and revokes access to a configurable Postgres role. You can tune this now, or adjust once we finish deployment.

Build the layer

Our handler uses the psycopg2 module to connect to Postgres, which requires packaging some native dependencies. To package the layer, run:

$ cd modules/postgres-lambda/layer
$ ./

Build the handler

The handler build just includes your local python code, because we've packaged all our dependencies in the layer.

$ cd modules/postgres-lambda/handler
$ ./

Local testing

You can iterate on your handler function locally by setting up a docker-compose based Postgres database and then invoking your handler function directly. We've provided tools to help with local testing in the test directory.

  1. Start the local database with docker-compose:
$ cd test
$ docker compose up -d
  1. Create a test user, database and role with
$ cd test
$ ./
  1. Copy env.example to the handler directory, and source it:
$ cp test/env.example .env
$ source .env
  1. Run pip install -r requirements.txt
  2. Now you can test the handler! Run cat test/escalate.json | python to grant a user access to the readonly role, and cat test/deescalate.json | python to revoke access to the role.
  3. Verify the user grants by running \du from the psql console.

Now that you've built and completed initial validation of your handler implemetnation, we can move on to Sym stuff! You'll need to work with the Sym team to get your organization set up with access to the Sym platform. Once you're onboarded, continue from here.

Install the symflow CLI

The symflow CLI is what you use to interact with Sym's control plane.

$ brew install symopsio/tap/symflow
==> Tapping symopsio/tap
Cloning into '/opt/homebrew/Library/Taps/symopsio/homebrew-tap'...
remote: Enumerating objects: 1148, done.
remote: Counting objects: 100% (285/285), done.
remote: Compressing objects: 100% (222/222), done.
remote: Total 1148 (delta 134), reused 156 (delta 59), pack-reused 863
Receiving objects: 100% (1148/1148), 324.27 KiB | 6.36 MiB/s, done.
Resolving deltas: 100% (530/530), done.
Tapped 14 formulae (43 files, 582.7KB).
==> Downloading
######################################################################## 100.0%
==> Installing symflow from symopsio/tap
🍺  /opt/homebrew/Cellar/symflow/1.3.7: 10,351 files, 198MB, built in 33 second


We'll have to login before we can do anything else. Sym also supports SSO, if your organization has set it up.

$ symflow login
Sym Org: healthy-health
Password: ************
MFA Token: ******

Success! Welcome, Sym Implementer. 🤓

Set your Org slug

You simply have to take the slug given to you by the Sym team, and set it in environments/prod/terraform.tfvars.

# environments/prod/terraform.tfvars

sym_org_slug = "healthy-health"

Now that you've got symflow installed, you need to install Sym's Slack app into your workspace.

Grab your Workspace ID

The easiest place to find this is in the URL you see when you run Slack in your web browser. It will start with a T, and look something like TABC123.

This also goes in environments/prod/terraform.tfvars.

# environments/prod/terraform.tfvars

slack_workspace_id = "TABC123"

Provision your Slack app

symflow has a convenient way to provision an instance of Sym's Slack app. This command will generate an install link that you can either use directly, or forward on to your Workspace Administrator.

$ symflow services create --service-type slack --external-id T123ABC
Successfully set up service type slack with external ID TABC123!
Generated an installation link for the Sym Slack app:

Please send this URL to an administrator who has permission to install the app. Or, if that's you, we can open it now.

Would you like to open the Slack installation URL in a browser window? [Y/n]:

Once Slack is set up, try launching the Sym app with /sym in Slack.

You should see a welcome modal like this one, since we haven't set up a Flow yet:

Slack Welcome Modal

This Flow is set up to route access requests to the #sym-requests channel. You can change this channel in—wait for it—terraform.tfvars.

Sym will also send any errors that happen during a Run (due to external failures or config issues) to a configurable error channel. You'll never guess where you can configure this.

# environments/prod/terraform.tfvars

flow_vars = {
  request_channel = "#sym-requests"

You can also change the channel that errors are routed to, which defaults to #sym-errors.

# environments/prod/terraform.tfvars

error_channel = "#sym-errors"

We've included an optional module that provisions an RDS database that you can use to test our the flow. If you want to use our test database, then set db_enabled to true in your terraform.tfvars file.

If you want to use an existing postgres database, no problem. In this case, you'll need to configure the lambda_subnet_ids and pg_connection-config variables so that your Lambda is in a VPC that can talk to your database.

Now that Slack is set up, let's provision your Flow!

$ export AWS_PROFILE=my-profile
$ cd app
$ terraform init
$ terraform apply
Plan: 69 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

Apply complete! Resources: 69 added, 0 changed, 0 destroyed.

Password Configuration

Once you run a terraform apply, you need to configure the database password that the handler should use to connect to the database. The handler looks up your database password in an AWS Parameter Store Parameter. The Parameter is named based on your Sym runtime name, like / You can set the value from the console or from the command line:

$ aws ssm put-parameter \
  --name / \
  --value "${PG_PASSWORD}" \
  --type SecureString \

If you're using the example-db module to test, you can get the database connection info using the terraform output command, see the README for more details.

Try out a request!

You should be able to make a request now with /sym req. Once approved, the postgres-lambda example function should be invoked in your AWS account, and you'll likely get an error because we haven't set up your Postgres role yet. That's OK! We'll deal with that next.

Sym invokes your Lambda function with a SymLogEntry payload. Read more about the properties of SymLogEntry in our API docs, or head over to the lambda-templates repo to see example Lambdas in action.

Configure database roles and users

The pg_targets variable in terraform.tfvars lists the database roles that users can request access to. You'll need to configure this to be a valid role value.

Our default setup uses a role named readonly. Here's how to set this role up to have read only access to a database named app:

CREATE ROLE readonly;

You may also need to do some user management! Sym will invoke your Lambda with the Slack email address of the requesting user. We've provided a starter implementation to map this user to a valid database user in the resolve_user method.

Updating the implementation code

Once you've run your Terraform pipeline, you can update the function code using the by specifying an environment argument:

$ cd handler
$ ./ -e prod

## E2E test and more goodies!
Duration: 5:00

### Tunneling for access
If you're using our example-db module, we've included a bastion instance that you can use to port foward your databse to localhost. The bastion uses AWS Session Manager, so no actual public endpoint is required!

Here we're forwarding our database endpoint to local port 5434:

$ ../../modules/example-db/ -e $ENDPOINT -l 5434 -n sym -r 5432
    "RequestId": "de45b5ef-5fe3-438f-9a0f-d853b252f7a9",
    "Success": true
Warning: Permanently added 'i-09aff5385d072cb84' (ED25519) to the list of known hosts.

Connecting to the database

After establishing a tunnel or VPN connection to the database, make a Sym access request! Once approved, you can log in using psql or whatever database client you prefer. Note that you should download the AWS Global Certificate Bundle to connect with psql:

$ psql "host=localhost port=5434 dbname=app user=sym_user password=$PGPASSWORD sslmode=verify-ca sslrootcert=/usr/local/aws-ca/global-bundle.pem"
psql (14.2, server 11.13)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
localhost sym_user@app=>

What's next?

Here are some next steps to consider: