pgperms

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 22, 2023 License: BSD-2-Clause Imports: 26 Imported by: 0

README

pgperms

pgperms allows you to manage your PostgreSQL permissions in a configuration file. This follows the configuration as code paradigm and allows you to declaratively manage your PostgreSQL grants.

Installation

go install github.com/SnoozeThis-org/pgperms/cmd/pgperms@latest

or grab it from our Releases.

Getting started

If you already have an existing PostgreSQL cluster running, you can create pgperms config file from that cluster through:

$ pgperms --dump --user postgres > pgperms.yaml

Then you can edit your config and see what changes need to be made:

$ pgperms --user postgres --config pgperms.yaml

And finally you can sync your config file to the cluster:

$ pgperms --user postgres --config pgperms.yaml --apply

The password can be read from your .pgpass, or prompted by using -W .

Managing roles

pgperms is the source of truth for all roles defined in its config file. When syncing, it will make those roles have exactly the specified permissions.

Any roles not listed in the config file are unmanaged, and will be completely ignored by pgperms.

To delete users, you have to list them as tombstoned users. (If you were to simply remove them from the config file, they'd become unmanaged users instead of being dropped.)

roles:
  yourname:
    password: SCRAM-SHA-256$4096:ICus8JAbG67BUVc+bifCBg==$3ULFbqx6ySVZJr51b6DOVQIbqy3GxrsHyxb/+JD0pag=:TJyct6ApBeiTdr+z7RP8CXtTOO5w+iK3NEervm9Ezb0=
    superuser: true
  rolegroup:
    login: false
  someonewithlotsofsettings:
    createdb: true
    createrole: true
    bypassrls: true
    inherit: true
    connectionlimit: 3
    valid_until: "2038-01-01 00:00:00"
    member_of: [rolegroup]
  replication:
    replication: true
tombstoned_roles:
- oldemployee

Managing databases and schemas

Though not exactly permissions, pgperms can also create/drop databases and schemas for you. This is to make it easy to bootstrap a new cluster with pgperms. Pgperms can create databases/schemas for you and immediately set the correct permissions on them.

To delete databases/schemas, you have to list them as tombstoned. (If you were to simply remove them from the config file, they'd become unmanaged instead of being dropped.)

databases:
- mydatabase
tombstoned_databases:
- unused_database

schemas:
- mydatabase.myschema
tombstoned_schemas:
- mydatabase.unused_schema

Permissions are configured like this:

database_privileges:
  - roles: [rolegroup]
    privileges: [CONNECT]
    databases:
      - mydatabase
  - roles: [someonewithlotsofsettings]
    privileges: [CONNECT, TEMPORARY]
    databases:
      - mydatabase

schema_privileges:
  - roles: [rolegroup]
    privileges: [USAGE]
    schemas:
      - mydatabase.myschema
  - roles: [someonewithlotsofsettings]
    privileges: [CREATE, USAGE]
    schemas:
      - mydatabase.myschema

Table permissions

Tables can't be created/dropped by pgperms. You can configure the permissions however.

You can use * as the table name to imply all tables in a schema.

You can also configure the permissions for views, materialized views, foreign tables and partitioned tables as if they were tables.

table_privileges:
  - roles: [rolegroup]
    privileges: [SELECT, INSERT, UPDATE]
    tables:
      - mydatabase.myschema.mytable
      - mydatabase.otherschema.*

Sequence permissions

Sequences can't be created/dropped by pgperms. You can configure the permissions however.

You can use * as the sequences name to imply all sequences in a schema.

sequence_privileges:
  - roles: [rolegroup]
    privileges: [SELECT, UPDATE, USAGE]
    sequences:
      - mydatabase.otherschema.*

Type and domain permissions

Types work similarly as the others. For the purposes of pgperms you should consider domains to simply be types.

Contributions

We'll happily accept your contributions! There's still a lot of things not supported:

  • Permissions on columns, foreign data wrappers, foreign servers, routines, large objects or tablespaces.
  • Set up default privileges so that newly created tables already have the correct permissions without having to run pgperms?
  • A config setting to automatically manage all users (and thus delete any unlisted users without needing to tombstone them).
  • More test cases

Development of pgperms is sponsored by SnoozeThis: a bot that can hold on to your blocked issues until they're actionable.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Dump

func Dump(ctx context.Context, conns *Connections) (string, error)

Dump all permissions from a running cluster and return a config yaml.

func Escape

func Escape(s string) string

Escape a string for use in a query. I don't fully guarantee this is correct, but it'll probably do for strings from the configuration.

func FetchRoles

func FetchRoles(ctx context.Context, conn *pgx.Conn) (map[string]RoleAttributes, error)

FetchRoles returns all roles and their attributes from a running PostgreSQL cluster.

func MD5Password added in v0.0.4

func MD5Password(username, password string) string

func ScramSha256Password added in v0.0.4

func ScramSha256Password(password string) (string, error)

func Sync

func Sync(ctx context.Context, conns *Connections, desired []byte, ss SyncSink) error

Sync the desired configuration to a running cluster. Queries to be executed are sent to the SyncSink, not executed on the given connections.

func SyncDatabases

func SyncDatabases(ss SyncSink, wanted, tombstoned, actual []string)

SyncDatabases tells the SyncSink which queries should be executed to create/delete the databases.

func SyncPrivileges

func SyncPrivileges(ss SyncSink, databases []string, actual, desired []GenericPrivilege)

SyncPrivileges tells the SyncSink which queries to execute to get towards the desired privileges.

func SyncRoles

func SyncRoles(ss SyncSink, oldRoles, newRoles map[string]RoleAttributes, tombstoned []string)

SyncRoles tells the SyncSink which queries should be executed to get to the desired state.

func SyncSchemas

func SyncSchemas(ss SyncSink, wanted, tombstoned, actual []string)

SyncSchemas tells the SyncSink which queries should be executed to create/delete the schemas.

func ValidateConfig

func ValidateConfig(c *Config) error

ValidateConfig checks whether the given config is correct.

Types

type Config

type Config struct {
	IgnoreSuperuserGrants *bool `yaml:"ignore_superuser_grants,omitempty"`

	Roles               map[string]RoleAttributes
	TombstonedRoles     []string `yaml:"tombstoned_roles,omitempty"`
	Databases           []string
	TombstonedDatabases []string `yaml:"tombstoned_databases,omitempty"`
	Schemas             []string
	TombstonedSchemas   []string `yaml:"tombstoned_schemas,omitempty"`

	DatabasePrivileges []GenericPrivilege `yaml:"database_privileges,omitempty"`
	SchemaPrivileges   []GenericPrivilege `yaml:"schema_privileges,omitempty"`
	TablePrivileges    []GenericPrivilege `yaml:"table_privileges,omitempty"`
	SequencePrivileges []GenericPrivilege `yaml:"sequence_privileges,omitempty"`
	// ColumnPrivileges             []GenericPrivilege `yaml:"column_privileges,omitempty"`
	// ForeignDataWrapperPrivileges []GenericPrivilege `yaml:"foreign_data_wrapper_privileges,omitempty"`
	// ForeignServerPrivileges      []GenericPrivilege `yaml:"foreign_server_privileges,omitempty"`
	// RoutinePrivileges            []GenericPrivilege `yaml:"routine_privileges,omitempty"`
	LanguagePrivileges []GenericPrivilege `yaml:"language_privileges,omitempty"`
	// LargeObjectPrivileges        []GenericPrivilege `yaml:"large_object_privileges,omitempty"`
	// TablespacePrivileges         []GenericPrivilege `yaml:"tablespace_privileges,omitempty"`
	TypePrivileges []GenericPrivilege `yaml:"type_privileges,omitempty"`
}

Config is the YAML format.

func Gather

func Gather(ctx context.Context, conns *Connections, interestingRoles, interestingDatabases []string) (*Config, error)

Gather all permissions from a running cluster.

func (Config) GetIgnoreSuperuserGrants

func (c Config) GetIgnoreSuperuserGrants() bool

type Connections

type Connections struct {
	// contains filtered or unexported fields
}

Connections is a set of connections to the same cluster, but connected to different databases.

func NewConnections

func NewConnections(ctx context.Context, primary *pgx.Conn) *Connections

NewConnections creates a new set of connections, starting with given connection as the primary connection. Other connections will be made based on its config.

func (*Connections) Close

func (c *Connections) Close()

Close all connections except for the primary.

func (*Connections) DropCachedConnection

func (c *Connections) DropCachedConnection(database string)

DropCachedConnection disconnects from the given database name if needed.

func (*Connections) Get

func (c *Connections) Get(database string) (*pgx.Conn, func(), error)

Get (or create) a connection to a specific database. You need to call the returned function when done with the connection.

type GenericPrivilege

type GenericPrivilege struct {
	Roles      []string `yaml:"roles,flow"`
	Privileges []string `yaml:"privileges,flow"`
	Grantable  bool     `yaml:"grantable,omitempty"`

	Tables              []string `yaml:"tables,omitempty"`
	Columns             []string `yaml:"columns,omitempty"`
	Sequences           []string `yaml:"sequences,omitempty"`
	Databases           []string `yaml:"databases,omitempty"`
	Domains             []string `yaml:"domains,omitempty"`
	ForeignDataWrappers []string `yaml:"foreign_data_wrappers,omitempty"`
	ForeignServers      []string `yaml:"foreign_servers,omitempty"`
	Routines            []string `yaml:"routines,omitempty"`
	Languages           []string `yaml:"languages,omitempty"`
	LargeObjects        []string `yaml:"large_objects,omitempty"`
	Schemas             []string `yaml:"schemas,omitempty"`
	Tablespaces         []string `yaml:"tablespaces,omitempty"`
	Types               []string `yaml:"types,omitempty"`
}

GenericPrivilege is a set of privileges for a set of roles on a set of targets.

type PasswordHasher added in v0.0.4

type PasswordHasher func(username, password string) (string, error)

func SelectPasswordHasher added in v0.0.4

func SelectPasswordHasher(ctx context.Context, conn *pgx.Conn) (PasswordHasher, error)

type QueryForDatabase added in v0.0.3

type QueryForDatabase struct {
	Database string
	Query    string
}

func (QueryForDatabase) String added in v0.0.3

func (q QueryForDatabase) String() string

type Recorder

type Recorder struct {
	// contains filtered or unexported fields
}

Recorder is a SyncSink that simply records all the queries.

func NewRecorder

func NewRecorder() *Recorder

func (*Recorder) AddBarrier

func (r *Recorder) AddBarrier()

func (*Recorder) Apply added in v0.0.3

func (r *Recorder) Apply(ctx context.Context, conns *Connections) error

func (*Recorder) Get

func (r *Recorder) Get() []QueryForDatabase

Get returns all queries recorded by this Recorder.

func (*Recorder) Query

func (r *Recorder) Query(database, query string)

Query records that a query should happen.

type RoleAttributes

type RoleAttributes struct {
	Superuser       bool       `yaml:"superuser,omitempty"`
	CreateDB        bool       `yaml:"createdb,omitempty"`
	CreateRole      bool       `yaml:"createrole,omitempty"`
	Inherit         *bool      `yaml:"inherit,omitempty"`
	Login           *bool      `yaml:"login,omitempty"`
	Replication     bool       `yaml:"replication,omitempty"`
	BypassRLS       bool       `yaml:"bypassrls,omitempty"`
	ConnectionLimit *int       `yaml:"connectionlimit,omitempty"`
	Password        *string    `yaml:"password,omitempty"`
	ValidUntil      *time.Time `yaml:"validuntil,omitempty"`
	MemberOf        []string   `yaml:"member_of,omitempty"`
	// contains filtered or unexported fields
}

RoleAttributes is a piece of configuration that describes which attributes a role should have.

func (RoleAttributes) CreateSQL

func (r RoleAttributes) CreateSQL(username string) string

CreateSQL returns the SQL to create this role.

func (RoleAttributes) GetConnectionLimit

func (r RoleAttributes) GetConnectionLimit() int

func (RoleAttributes) GetInherit

func (r RoleAttributes) GetInherit() bool

func (RoleAttributes) GetLogin

func (r RoleAttributes) GetLogin() bool

func (RoleAttributes) GetValidUntil

func (r RoleAttributes) GetValidUntil() time.Time

type SyncSink

type SyncSink interface {
	// Query is called when a query should be executed (in the given database) to get to the desired state.
	// Query can also be called with database "", indicating it can be run on any database.
	Query(database, query string)

	// AddBarrier is called between queries to indicate they can't be reordered across the barrier.
	// Implementations can safely ignore calls to AddBarrier, unless stable output is required (like the tests).
	AddBarrier()
}

SyncSink will be called for every query that should be executed to get to the desired state.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL