Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinschoonover committed Jul 13, 2022
1 parent 8e43506 commit bb66231
Show file tree
Hide file tree
Showing 14 changed files with 1,205 additions and 1 deletion.
21 changes: 21 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# TODO: automate generation of this file
version: 2
updates:
- package-ecosystem: github-actions
directory: /
labels:
- dependencies
- actions
- Skip Changelog
schedule:
interval: weekly
day: sunday
- package-ecosystem: gomod
directory: /
labels:
- dependencies
- go
- Skip Changelog
schedule:
interval: weekly
day: sunday
32 changes: 32 additions & 0 deletions .github/workflows/auto-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Dependabot Pull Request Approve and Merge

on: pull_request_target

permissions:
pull-requests: write
contents: write

jobs:
dependabot:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- name: Dependabot metadata
id: dependabot-metadata
uses: dependabot/[email protected]
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
# Here the PR gets approved.
- name: Approve a PR
run: gh pr review --approve "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Finally, this sets the PR to allow auto-merging for patch and minor
# updates if all checks pass
- name: Enable auto-merge for Dependabot PRs
if: ${{ steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' }}
run: gh pr merge --auto --squash "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 changes: 30 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: goreleaser

on:
push:
tags:
- "*"

jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.18
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
# either 'goreleaser' (default) or 'goreleaser-pro'
distribution: goreleaser
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution
# GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
20 changes: 20 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: test

on:
push:
branches: [main]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
go: ["1.17", "1.18"]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
cache: true
- run: go test ./...
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@

# Dependency directories (remove the comment below to include it)
# vendor/
#
bin
33 changes: 33 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
# you may remove this if you don't need go generate
- go generate ./...
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
main: ./planetscale-database-plugin/
archives:
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: "checksums.txt"
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
31 changes: 31 additions & 0 deletions Earthfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
VERSION 0.6
FROM golang:1.18
WORKDIR /vault-plugin-database-planetscale

deps:
COPY go.mod go.sum ./
RUN go mod download
SAVE ARTIFACT go.mod AS LOCAL go.mod
SAVE ARTIFACT go.sum AS LOCAL go.sum

build:
FROM +deps
COPY *.go .
COPY --dir ./planetscale-database-plugin .
RUN CGO_ENABLED=0 go build -o bin/vault-plugin-database-planetscale planetscale-database-plugin/main.go
SAVE ARTIFACT bin/vault-plugin-database-planetscale /auth0 AS LOCAL bin/vault-plugin-database-planetscale

test:
FROM +deps
COPY *.go .
ARG TEST_AUTH0_DOMAIN=https://test-stratos-host.us.auth0.com
RUN --secret TEST_AUTH0_ACCESS_TOKEN TEST_AUTH0_DOMAIN=$TEST_AUTH0_DOMAIN CGO_ENABLED=0 go test github.com/bloominlabs/vault-plugin-database-planetscale

dev:
BUILD +build
LOCALLY
RUN bash ./scripts/dev.sh

all:
BUILD +build
BUILD +test
100 changes: 99 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,100 @@
# vault-plugin-database-planetscale
generate @planetscale usernames and passwords using @vault

Generate @planetscale usernames and passwords using vault.

## Usage

### Setup Endpoint

1. Download and enable plugin locally

```bash
vault secrets enable database
vault write sys/plugins/catalog/database/vault-plugin-database-planetscale \
sha256=<SHA256SUM of plugin> \
command="vault-plugin-database-planetscale"
```

2. Configure a database the plugin

```bash
# you can generate a service token withhttps://docs.planetscale.com/concepts/service-tokens
vault write database/config/planetscale \
plugin_name=vualt-plugin-database-planetscale \
allowed_roles="admin" \
organization="<your organization>" \
database="<your database>" \
service_token="<service_token>" \
service_token_id="<service_token_id>"

```

3. Configure a role

```bash
vault write database/roles/admin \
db_name=$MNT_PATH \
creation_statements='{"branch": "main", "role": "admin"}' \
default_ttl="1h" \
max_ttl="24h"
```

### Configure Role

Roles are have a configurable 'branch' and 'role' that you can specifying using the `creation_statements` parameter

```bash
vault write database/roles/admin \
db_name=$MNT_PATH \
creation_statements='{"branch": "main", "role": "admin"}' \
default_ttl="1h" \
max_ttl="24h"
```

### Rotating the Root Token

The is not currently implemented, but will be added in the future.

### Generate a new Token

To generate a new token:

[Configure a Role](#configure-role) and perform a 'read' operation on the `creds/<role-name>` endpoint.

```bash
# To read data using the api
$ vault read database/creds/admin
Key Value
--- -----
lease_id database/creds/admin/p2rG2nCorEVTUTVpXnb0NHsh
lease_duration 1h
lease_renewable true
password <password>
username v-token-admin-qrez41hrdjt3n1zviwaz-1657678284
```

## Development

The provided [Earthfile] ([think makefile, but using
docker](https://earthly.dev)) is used to build, test, and publish the plugin.
See the build targets for more information. Common targets include

```bash
# build a local version of the plugin
$ earthly +build

# execute integration tests
#
# use https://developers.auth0.com/api/tokens/create to create a token
# with 'User:API Tokens:Edit' permissions
$ TEST_auth0_TOKEN=<YOUR_auth0_TOKEN> earthly --secret TEST_auth0_TOKEN +test

# start vault and enable the plugin locally
earthly +dev
```

[vault]: https://www.vaultproject.io/
[auth0-management-api-tokens]: https://auth0.com/docs/security/tokens/access-tokens/management-api-access-tokens
[earthfile]: ./Earthfile
[secrets plugin]: https://www.vaultproject.io/docs/secrets
[database plugin]: https://www.vaultproject.io/docs/secrets/databases
117 changes: 117 additions & 0 deletions connection_producer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package planetscale

import (
"context"
"fmt"
"sync"

"github.com/hashicorp/vault/sdk/database/helper/connutil"
"github.com/mitchellh/mapstructure"
"github.com/planetscale/planetscale-go/planetscale"
)

type ConnectionParameters struct {
Organization string `json:"organization" structs:"organization" mapstructure:"organization"`
Database string `json:"database" structs:"database" mapstructure:"database"`
}

// planetscaleConnectionProducer implements ConnectionProducer and provides an
// interface for databases to make connections.
type planetscaleConnectionProducer struct {
Organization string `json:"organization" structs:"organization" mapstructure:"organization"`
Database string `json:"database" structs:"database" mapstructure:"database"`
ServiceToken string `json:"service_token" structs:"service_token" mapstructure:"service_token"`
TokenName string `json:"token_name" structs:"token_name" mapstructure:"token_name"`

Initialized bool
RawConfig map[string]interface{}
Type string
client *planetscale.Client
sync.Mutex
}

func (c *planetscaleConnectionProducer) Init(ctx context.Context, conf map[string]interface{}, verifyConnection bool) (map[string]interface{}, error) {
c.Lock()
defer c.Unlock()

c.RawConfig = conf

err := mapstructure.WeakDecode(conf, &c)
if err != nil {
return nil, err
}

if c.Organization == "" {
return nil, fmt.Errorf("organization cannot be empty")
}
if c.Database == "" {
return nil, fmt.Errorf("database cannot be empty")
}
if c.ServiceToken == "" {
return nil, fmt.Errorf("service_token cannot be empty")
}
if c.TokenName == "" {
return nil, fmt.Errorf("token_name cannot be empty")
}

client, err := c.createClient(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create planetscale client: %w", err)
}
c.client = client

// Set initialized to true at this point since all fields are set,
// and the connection can be established at a later time.
c.Initialized = true

return c.RawConfig, nil
}

// Connection creates or returns an existing a database connection. If the session fails
// on a ping check, the session will be closed and then re-created.
// This method does locks the mutex on its own.
func (c *planetscaleConnectionProducer) Connection(ctx context.Context) (*planetscale.Client, error) {
if !c.Initialized {
return nil, connutil.ErrNotInitialized
}

c.Mutex.Lock()
defer c.Mutex.Unlock()

if c.client != nil {
return c.client, nil
}

client, err := c.createClient(ctx)
if err != nil {
return nil, err
}
c.client = client
return c.client, nil
}

func (c *planetscaleConnectionProducer) createClient(ctx context.Context) (client *planetscale.Client, err error) {
client, err = planetscale.NewClient(
planetscale.WithServiceToken(c.TokenName, c.ServiceToken),
)
if err != nil {
return nil, err
}
return client, nil
}

// Close terminates the database connection.
func (c *planetscaleConnectionProducer) Close() error {
c.Lock()
defer c.Unlock()

c.client = nil

return nil
}

func (c *planetscaleConnectionProducer) secretValues() map[string]string {
return map[string]string{
c.ServiceToken: "[ServiceToken]",
}
}
Loading

0 comments on commit bb66231

Please sign in to comment.