Skip to content

Commit

Permalink
Add a read-only role to the block metrics database (#376)
Browse files Browse the repository at this point in the history
* Add a read-only role to the block metrics database

* Use schema permissioned in order to achieve read-only access for the readonly role

* Bump version to v0.9.8

Co-authored-by: Dave Puchyr <[email protected]>
  • Loading branch information
davepuchyr and Dave Puchyr authored Nov 12, 2020
1 parent b25850f commit ea2f529
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 21 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## HEAD

## v0.9.8

- BLOCK METRICS: Move tables to schema 'permissioned' and grant read-only access to role readonly

## v0.9.7

- CLI: Add update-fee command
Expand Down
2 changes: 1 addition & 1 deletion cmd/block-metrics/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ services:
depends_on:
- pgadmin4
image: alpine
command: echo -e "\n\nUse Firefox or get 'The CSRF session token is missing.' errors.\nBrowse to http://localhost:1111 to access pgadmin.\nLogin with '[email protected]/test'.\nConnect to server 'db' as 'postgres/root'.\n\n"
command: echo -e "\n\nBrowse to http://localhost:1111 to access pgadmin.\nHard-reload the page if you get 'The CSRF session token is missing.' errors.\nLogin with '[email protected]/test'.\nConnect to server 'db' as 'postgres/root'.\n\n"
7 changes: 4 additions & 3 deletions cmd/block-metrics/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ func main() {
DBHost: os.Getenv("POSTGRES_HOST"),
DBName: os.Getenv("POSTGRES_DB"),
DBPass: os.Getenv("POSTGRES_PASSWORD"),
DBROPass: os.Getenv("POSTGRES_RO_PASSWORD"),
DBROUser: os.Getenv("POSTGRES_RO_USER"),
DBSSL: os.Getenv("POSTGRES_SSL_ENABLE"),
DBUser: os.Getenv("POSTGRES_USER"),
FeeDenom: os.Getenv("FEE_DENOMINATION"),
Expand All @@ -37,15 +39,14 @@ func run(conf pkg.Configuration) error {
return fmt.Errorf("ensure database: %s", err)
}

dbUri := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", conf.DBUser, conf.DBPass,
conf.DBHost, conf.DBName, conf.DBSSL)
dbUri := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", conf.DBUser, conf.DBPass, conf.DBHost, conf.DBName, conf.DBSSL)
db, err := sql.Open("postgres", dbUri)
if err != nil {
return fmt.Errorf("cannot connect to postgres: %s", err)
}
defer db.Close()

if err := pkg.EnsureSchema(db); err != nil {
if err := pkg.EnsureSchema(db, conf.DBName, conf.DBROUser, conf.DBROPass); err != nil {
return fmt.Errorf("ensure schema: %s", err)
}

Expand Down
9 changes: 7 additions & 2 deletions cmd/block-metrics/pkg/config.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package pkg

type Configuration struct {
// database
DBHost string
DBUser string
DBPass string
DBName string
DBSSL string
// Read-write user
DBUser string
DBPass string
// Read-only user
DBROUser string
DBROPass string
// Denomination of the fee coin, eg uiov
FeeDenom string
// Tendermint light client daemon URL
Expand Down
66 changes: 51 additions & 15 deletions cmd/block-metrics/pkg/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,45 @@ func EnsureDatabase(user, password, host, database, ssl string) error {
return nil
}

func EnsureSchema(pg *sql.DB) error {
// deal with the pesky TYPE 'action' that doesn't allow an IF NOT EXISTS clause
func EnsureSchema(pg *sql.DB, database, rouser, ropassword string) error {
// setup readonly on schema permissioned a la https://aws.amazon.com/blogs/database/managing-postgresql-users-and-roles/
rows, err := pg.Query(`
SELECT schema_name
FROM information_schema.schemata
WHERE schema_name = 'permissioned';
`)
if err != nil {
return fmt.Errorf("type query: %s", err)
}
if !rows.Next() {
sql := `
CREATE SCHEMA permissioned;
ALTER DATABASE __db__ SET search_path = "$user", permissioned, public
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
REVOKE ALL ON DATABASE __db__ FROM PUBLIC;
CREATE ROLE readonly;
GRANT CONNECT ON DATABASE __db__ TO readonly;
GRANT USAGE ON SCHEMA permissioned TO readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA permissioned GRANT SELECT ON TABLES TO readonly;
CREATE USER __rouser__ WITH PASSWORD '__ropassword__';
GRANT readonly TO __rouser__;
`
replacements := make(map[string]string)
replacements["__db__"] = database
replacements["__rouser__"] = rouser
replacements["__ropassword__"] = ropassword
for key, value := range replacements {
sql = strings.ReplaceAll(sql, key, value)
}
for _, query := range strings.Split(sql, "\n") {
if _, err := pg.Exec(query); err != nil {
return &QueryError{Query: query, Err: err}
}
}
}

// deal with the pesky TYPE 'action' that doesn't allow an IF NOT EXISTS clause
rows, err = pg.Query(`
SELECT pg_type.typname, pg_enum.enumlabel
FROM pg_type
JOIN pg_enum ON pg_enum.enumtypid = pg_type.oid
Expand Down Expand Up @@ -61,25 +97,25 @@ func EnsureSchema(pg *sql.DB) error {
}

var schema = `
CREATE TABLE IF NOT EXISTS blocks (
CREATE TABLE IF NOT EXISTS permissioned.blocks (
block_height BIGINT NOT NULL PRIMARY KEY,
block_hash TEXT NOT NULL UNIQUE,
block_time TIMESTAMPTZ NOT NULL,
fee_frac BIGINT NOT NULL
);
---
CREATE TABLE IF NOT EXISTS transactions (
CREATE TABLE IF NOT EXISTS permissioned.transactions (
id BIGSERIAL PRIMARY KEY,
transaction_hash TEXT NOT NULL UNIQUE,
block_id BIGINT NOT NULL REFERENCES blocks(block_height),
block_id BIGINT NOT NULL REFERENCES permissioned.blocks(block_height),
signatures BYTEA ARRAY,
fee JSONB,
memo text
);
---
CREATE TABLE IF NOT EXISTS domains (
CREATE TABLE IF NOT EXISTS permissioned.domains (
id BIGSERIAL PRIMARY KEY,
name TEXT,
admin TEXT NOT NULL,
Expand All @@ -89,9 +125,9 @@ CREATE TABLE IF NOT EXISTS domains (
);
---
CREATE TABLE IF NOT EXISTS accounts (
CREATE TABLE IF NOT EXISTS permissioned.accounts (
id BIGSERIAL PRIMARY KEY,
domain_id BIGINT NOT NULL REFERENCES domains(id),
domain_id BIGINT NOT NULL REFERENCES permissioned.domains(id),
name TEXT,
owner TEXT NOT NULL,
metadata TEXT,
Expand All @@ -100,25 +136,25 @@ CREATE TABLE IF NOT EXISTS accounts (
);
---
CREATE TABLE IF NOT EXISTS resources (
CREATE TABLE IF NOT EXISTS permissioned.resources (
id BIGSERIAL PRIMARY KEY,
account_id BIGINT REFERENCES accounts(id),
account_id BIGINT REFERENCES permissioned.accounts(id),
uri TEXT,
resource TEXT
);
---
CREATE TABLE IF NOT EXISTS certificates (
CREATE TABLE IF NOT EXISTS permissioned.certificates (
id BIGSERIAL PRIMARY KEY,
account_id BIGINT REFERENCES accounts(id),
account_id BIGINT REFERENCES permissioned.accounts(id),
certificate BYTEA
);
---
CREATE TABLE IF NOT EXISTS product_fees (
CREATE TABLE IF NOT EXISTS permissioned.product_fees (
id BIGSERIAL PRIMARY KEY,
block BIGINT REFERENCES blocks(block_height),
account_id BIGINT REFERENCES accounts(id),
block BIGINT REFERENCES permissioned.blocks(block_height),
account_id BIGINT REFERENCES permissioned.accounts(id),
action action,
fee BIGINT,
payer TEXT,
Expand Down

0 comments on commit ea2f529

Please sign in to comment.