Skip to content
Merged
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
88 changes: 27 additions & 61 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ jobs:
POSTGRES_PASSWORD: secretpassword
env:
BATON_LOG_LEVEL: debug
BATON_STORAGE_ENGINE: pebble
BATON_DSN: "postgres://postgres:secretpassword@localhost:5432/postgres"
CONNECTOR_GRANT: "grant:entitlement:role:3375:member:role:10"
CONNECTOR_ENTITLEMENT: "entitlement:role:3375:member"
CONNECTOR_PRINCIPAL: "role:10"
CONNECTOR_PRINCIPAL_TYPE: "role"
Expand All @@ -70,64 +68,32 @@ jobs:
# env:
# PGPASSWORD: secretpassword
# run: psql -h localhost --user postgres -f test/ci.sql
- name: Install baton
run: ./scripts/get-baton.sh && mv baton /usr/local/bin
- name: Build baton-postgresql
run: go build ./cmd/baton-postgresql
- name: Run baton-postgresql
run: ./baton-postgresql

- name: Grant entitlement
run: ./baton-postgresql --grant-entitlement "${{ env.CONNECTOR_ENTITLEMENT }}" --grant-principal "${{ env.CONNECTOR_PRINCIPAL }}" --grant-principal-type "${{ env.CONNECTOR_PRINCIPAL_TYPE }}"

- name: Check grant was granted
run: ./baton-postgresql && baton grants --entitlement "${{ env.CONNECTOR_ENTITLEMENT }}" --output-format=json | jq --exit-status --arg principal "${{ env.CONNECTOR_PRINCIPAL }}" 'any(.grants[]?; .principal.id.resource == $principal)'

- name: Revoke grant
run: ./baton-postgresql --revoke-grant "${{ env.CONNECTOR_GRANT }}"

- name: Check grant was revoked
run: ./baton-postgresql && baton grants --entitlement "${{ env.CONNECTOR_ENTITLEMENT }}" --output-format=json | jq --exit-status --arg principal "${{ env.CONNECTOR_PRINCIPAL }}" 'any(.grants[]?; .principal.id.resource == $principal) | not'

- name: Grant entitlement
run: ./baton-postgresql --grant-entitlement "${{ env.CONNECTOR_ENTITLEMENT }}" --grant-principal "${{ env.CONNECTOR_PRINCIPAL }}" --grant-principal-type "${{ env.CONNECTOR_PRINCIPAL_TYPE }}"

- name: Check grant was re-granted
run: ./baton-postgresql && baton grants --entitlement "${{ env.CONNECTOR_ENTITLEMENT }}" --output-format=json | jq --exit-status --arg principal "${{ env.CONNECTOR_PRINCIPAL }}" 'any(.grants[]?; .principal.id.resource == $principal)'

- name: Create user
run: ./baton-postgresql --create-account-login "${{ env.CONNECTOR_NEW_USER }}"

- name: Check user was created
run: ./baton-postgresql && baton resources -o json | jq -e --arg login "${{ env.CONNECTOR_NEW_USER }}" 'any(.resources[].resource.annotations[]?;.["@type"]=="type.googleapis.com/c1.connector.v2.UserTrait" and .login==$login)'

- name: Fetch user id
shell: bash
run: |
set -euo pipefail
NEW_USER_ID=$(baton resources -t role -o json | jq -r --arg login "${{ env.CONNECTOR_NEW_USER }}" '.resources[].resource | select(any(.annotations[]?; .["@type"]=="type.googleapis.com/c1.connector.v2.UserTrait" and .login==$login)) | .id.resource')
if [ -z "$NEW_USER_ID" ]; then
echo "No role found for login '$CONNECTOR_NEW_USER'" >&2
exit 1
fi
if [ "$(printf '%s\n' "$NEW_USER_ID" | wc -l)" -ne 1 ]; then
echo "Expected exactly one role for login '$CONNECTOR_NEW_USER', but found multiple matches" >&2
exit 1
fi
echo "NEW_USER_ID=$NEW_USER_ID" >> "$GITHUB_ENV"

- name: Grant role to user
run: ./baton-postgresql --grant-entitlement "${{ env.CONNECTOR_ENTITLEMENT }}" --grant-principal "${{ env.NEW_USER_ID }}" --grant-principal-type "${{ env.CONNECTOR_PRINCIPAL_TYPE }}"

- name: Check role was granted
run: ./baton-postgresql && baton grants --entitlement "${{ env.CONNECTOR_ENTITLEMENT }}" -o json | jq -e --arg login "${{ env.CONNECTOR_NEW_USER }}" 'any(.grants[]?; any(.principal.annotations[]?; .["@type"]=="type.googleapis.com/c1.connector.v2.UserTrait" and .login==$login) or any(.grant.principal.annotations[]?; .["@type"]=="type.googleapis.com/c1.connector.v2.UserTrait" and .login==$login))'

- name: Delete user
run: ./baton-postgresql --delete-resource "${{ env.NEW_USER_ID }}" --delete-resource-type "${{ env.CONNECTOR_PRINCIPAL_TYPE }}"

- name: Check user was deleted
run: ./baton-postgresql && baton resources -o json | jq -e --arg login "${{ env.CONNECTOR_NEW_USER }}" 'any(.resources[].resource.annotations[]?;.["@type"]=="type.googleapis.com/c1.connector.v2.UserTrait" and .login==$login) | not'

# TODO: get correct role id using baton CLI
# - name: Rotate credentials for user
# run: ./baton-postgresql --rotate-credentials 'role:16384' --rotate-credentials-type 'role'
# Both shared actions install the latest released baton CLI; the action
# has no version pin or checksum. That is fine for reading this
# connector's c1z files: a newer CLI reads older files, and the pinned
# SDK is a released version, so the latest CLI is at least as new as the
# connector's c1z writer once that release's assets are published.
- name: Test connector syncing
uses: ConductorOne/github-workflows/actions/sync-test@v4
Comment thread
jugonzalez12 marked this conversation as resolved.
with:
connector: ./baton-postgresql
baton-entitlement: ${{ env.CONNECTOR_ENTITLEMENT }}
baton-principal: ${{ env.CONNECTOR_PRINCIPAL }}
baton-principal-type: ${{ env.CONNECTOR_PRINCIPAL_TYPE }}
Comment thread
jugonzalez12 marked this conversation as resolved.
# The unauthenticated-sync check only auto-invalidates credential-
# looking variables, and BATON_DSN is not one, so hand it a bad DSN.
# The connector maps the resulting SQLSTATE 28P01 to Unauthenticated
# (exit 16), which is what the check accepts.
bad-credentials: BATON_DSN=postgres://bad:bad@localhost:5432/postgres
- name: Test account provisioning
uses: ConductorOne/github-workflows/actions/account-provisioning@v4
with:
connector: ./baton-postgresql
account-login: ${{ env.CONNECTOR_NEW_USER }}
# The SDK validates this as an address, and the connector names the
# created role after it, so the lookup has to go by email.
account-email: ${{ env.CONNECTOR_NEW_USER }}@example.com
account-type: ${{ env.CONNECTOR_PRINCIPAL_TYPE }}
search-method: email
Comment thread
jugonzalez12 marked this conversation as resolved.
42 changes: 9 additions & 33 deletions cmd/baton-postgresql/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ package main

import (
"context"
"fmt"
"os"

cfg "github.com/conductorone/baton-postgresql/pkg/config"
"github.com/conductorone/baton-sdk/pkg/cli"
"github.com/conductorone/baton-sdk/pkg/connectorbuilder"
"github.com/conductorone/baton-sdk/pkg/connectorrunner"
"github.com/conductorone/baton-sdk/pkg/types"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
"go.uber.org/zap"

"github.com/conductorone/baton-postgresql/pkg/connector"
configschema "github.com/conductorone/baton-sdk/pkg/config"
Expand All @@ -21,38 +17,18 @@ var version = "dev"
func main() {
ctx := context.Background()

// The capabilities subcommand runs without a DSN: it builds the zero-value
// connector instead of validating the required flag.
_, cmd, err := configschema.DefineConfiguration(ctx, "baton-postgresql", getConnector, cfg.Config,
connectorrunner.WithDefaultCapabilitiesConnectorBuilder(&connector.Postgresql{}))
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}

cmd.Version = version

err = cmd.Execute()
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
// The capabilities subcommand runs without a DSN, so it builds the
// zero-value connector instead of validating the required flag.
configschema.RunConnector(ctx, "baton-postgresql", version, cfg.Config, getConnector,
connectorrunner.WithDefaultCapabilitiesConnectorBuilderV2(&connector.Postgresql{}))
}

func getConnector(ctx context.Context, pgc *cfg.Postgresql) (types.ConnectorServer, error) {
l := ctxzap.Extract(ctx)

func getConnector(ctx context.Context, pgc *cfg.Postgresql, _ *cli.ConnectorOpts) (connectorbuilder.ConnectorBuilderV2, []connectorbuilder.Opt, error) {
// RunConnector prints the returned error on exit, so don't log it here.
cb, err := connector.New(ctx, pgc.Dsn, pgc.Schemas, pgc.IncludeColumns, pgc.IncludeLargeObjects, pgc.SyncAllDatabases, pgc.SkipBuiltInFunctions)
if err != nil {
l.Error("error creating connector", zap.Error(err))
return nil, err
}

newConnector, err := connectorbuilder.NewConnector(ctx, cb)
if err != nil {
l.Error("error creating connector", zap.Error(err))
return nil, err
return nil, nil, err
}

return newConnector, nil
return cb, nil, nil
}
44 changes: 23 additions & 21 deletions pkg/connector/column.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/conductorone/baton-postgresql/pkg/postgres"
v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
"github.com/conductorone/baton-sdk/pkg/annotations"
"github.com/conductorone/baton-sdk/pkg/pagination"
"github.com/conductorone/baton-sdk/pkg/types/resource"
)

var columnResourceType = &v2.ResourceType{
Expand All @@ -26,30 +26,31 @@ func (r *columnSyncer) ResourceType(ctx context.Context) *v2.ResourceType {
return columnResourceType
}

func (r *columnSyncer) List(ctx context.Context, parentResourceID *v2.ResourceId, pToken *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) {
func (r *columnSyncer) List(ctx context.Context, parentResourceID *v2.ResourceId, opts resource.SyncOpAttrs) ([]*v2.Resource, *resource.SyncOpResults, error) {
var err error
pToken := &opts.PageToken

if parentResourceID == nil {
return nil, "", nil, nil
return nil, &resource.SyncOpResults{}, nil
}

if parentResourceID.ResourceType != tableResourceType.Id {
return nil, "", nil, fmt.Errorf("invalid parent resource ID on column %s %s", parentResourceID.ResourceType, parentResourceID.Resource)
return nil, nil, fmt.Errorf("invalid parent resource ID on column %s %s", parentResourceID.ResourceType, parentResourceID.Resource)
}

db, parentID, err := parseWithDatabaseID(parentResourceID.Resource)
if err != nil {
return nil, "", nil, err
return nil, nil, err
}

client, _, err := r.clientPool.Get(ctx, db)
if err != nil {
return nil, "", nil, err
return nil, nil, err
}

columns, nextPageToken, err := client.ListColumns(ctx, parentID, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
if err != nil {
return nil, "", nil, err
return nil, nil, err
}

var ret []*v2.Resource
Expand All @@ -67,46 +68,47 @@ func (r *columnSyncer) List(ctx context.Context, parentResourceID *v2.ResourceId
})
}

return ret, nextPageToken, nil, nil
return ret, &resource.SyncOpResults{NextPageToken: nextPageToken}, nil
}

func (r *columnSyncer) Entitlements(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Entitlement, string, annotations.Annotations, error) {
func (r *columnSyncer) Entitlements(ctx context.Context, res *v2.Resource, _ resource.SyncOpAttrs) ([]*v2.Entitlement, *resource.SyncOpResults, error) {
col := &postgres.ColumnModel{}
ens, err := entitlementsForPrivs(ctx, resource, col.AllPrivileges())
ens, err := entitlementsForPrivs(ctx, res, col.AllPrivileges())
if err != nil {
return nil, "", nil, err
return nil, nil, err
}

return ens, "", nil, nil
return ens, &resource.SyncOpResults{}, nil
}

func (r *columnSyncer) Grants(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) {
db, tID, cID, err := parseColumnID(resource.Id.Resource)
func (r *columnSyncer) Grants(ctx context.Context, res *v2.Resource, opts resource.SyncOpAttrs) ([]*v2.Grant, *resource.SyncOpResults, error) {
pToken := &opts.PageToken
db, tID, cID, err := parseColumnID(res.Id.Resource)
if err != nil {
return nil, "", nil, err
return nil, nil, err
}

client, _, err := r.clientPool.Get(ctx, db)
if err != nil {
return nil, "", nil, err
return nil, nil, err
}

roles, nextPageToken, err := client.ListRoles(ctx, &postgres.Pager{Token: pToken.Token, Size: pToken.Size})
if err != nil {
return nil, "", nil, err
return nil, nil, err
}

col, err := client.GetColumn(ctx, tID, cID)
if err != nil {
return nil, "", nil, err
return nil, nil, err
}

ret, err := roleGrantsForPrivileges(ctx, client, resource, roles, col)
ret, err := roleGrantsForPrivileges(ctx, client, res, roles, col)
if err != nil {
return nil, "", nil, err
return nil, nil, err
}

return ret, nextPageToken, nil, nil
return ret, &resource.SyncOpResults{NextPageToken: nextPageToken}, nil
}

func newColumnSyncer(ctx context.Context, c *postgres.ClientDatabasesPool) *columnSyncer {
Expand Down
4 changes: 2 additions & 2 deletions pkg/connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ type Postgresql struct {
skipBuiltInFunctions bool
}

func (o *Postgresql) ResourceSyncers(ctx context.Context) []connectorbuilder.ResourceSyncer {
return []connectorbuilder.ResourceSyncer{
func (o *Postgresql) ResourceSyncers(ctx context.Context) []connectorbuilder.ResourceSyncerV2 {
return []connectorbuilder.ResourceSyncerV2{
newRoleSyncer(ctx, o.clientPool.Default(ctx)),
newSchemaSyncer(ctx, o.clientPool),
newTableSyncer(ctx, o.clientPool, o.includeColumns),
Expand Down
Loading
Loading