Fix continuous aggregate privileges during update

Copy ACL privileges (grants) from the query view (user-facing object)
to the internal objects (e.g., materialized hypertable, direct view,
and partial view) when updating the extension to the new version. A
previous change added such propagation of privileges when executing
`GRANT` statements, but didn't apply it retrospectively to the
internal objects of existing continuous aggregates.

Having the right permissions on internal objects is also necessary for
the watermark function used by real-time aggregation since it queries
the materialized hypertable directly.

The update script copies the ACL information from the user-facing view
of every continuous aggregate to its internal objects (including the
materialized hypertable and its chunks). This is done by direct insert
into `pg_class` instead of executing a `GRANT` statement in the update
script, since the latter will record the grant/ACL as an init
privilege (i.e., the system believes the GRANT is for an extension
object). The init privilege will prevent this ACL from being included
in future dump files, since `pg_dump` only includes non-init
privileges as it believes such privileges will be recreated with the
extension.

Fixes #2825
This commit is contained in:
Erik Nordström 2021-01-20 11:26:11 +01:00 committed by Erik Nordström
parent 126f1c815f
commit 86f8e30ae0
7 changed files with 132 additions and 2 deletions

View File

@ -8,6 +8,7 @@ accidentally triggering the load of a previous DB version.**
**Bugfixes**
* #2842 Do not mark job as started when seting next_start field
* #2845 Fix continuous aggregate privileges during upgrade
**Minor features**
* #2736 Support adding columns to hypertables with compression enabled

View File

@ -172,6 +172,13 @@ docker_run ${CONTAINER_ORIG} ${UPDATE_FROM_IMAGE}:${UPDATE_FROM_TAG}
docker_run ${CONTAINER_CLEAN_RESTORE} ${UPDATE_TO_IMAGE}:${UPDATE_TO_TAG}
docker_run ${CONTAINER_CLEAN_RERUN} ${UPDATE_TO_IMAGE}:${UPDATE_TO_TAG}
# Create roles for test. Roles must be created outside of regular
# setup scripts; they must be added separately to each instance since
# roles are not dumped by pg_dump.
docker_pgscript ${CONTAINER_ORIG} /src/test/sql/updates/setup.roles.sql "postgres"
docker_pgscript ${CONTAINER_CLEAN_RESTORE} /src/test/sql/updates/setup.roles.sql "postgres"
docker_pgscript ${CONTAINER_CLEAN_RERUN} /src/test/sql/updates/setup.roles.sql "postgres"
CLEAN_VOLUME=$(docker inspect ${CONTAINER_CLEAN_RESTORE} --format='{{range .Mounts }}{{.Name}}{{end}}')
UPDATE_VOLUME=$(docker inspect ${CONTAINER_ORIG} --format='{{range .Mounts }}{{.Name}}{{end}}')
@ -185,7 +192,7 @@ docker rm -f ${CONTAINER_ORIG}
echo "Running update container"
docker_run_vol ${CONTAINER_UPDATED} ${UPDATE_VOLUME}:/var/lib/postgresql/data ${UPDATE_TO_IMAGE}:${UPDATE_TO_TAG}
echo "Executing ALTER EXTENSION timescaledb UPDATE"
echo "Executing ALTER EXTENSION timescaledb UPDATE ($UPDATE_FROM_TAG -> $UPDATE_TO_TAG)"
docker_pgcmd ${CONTAINER_UPDATED} "ALTER EXTENSION timescaledb UPDATE" "single"
docker_pgcmd ${CONTAINER_UPDATED} "ALTER EXTENSION timescaledb UPDATE" "dn1"
# Need to update also postgres DB since add_data_node may connect to
@ -225,4 +232,5 @@ docker_pgcmd ${CONTAINER_CLEAN_RESTORE} "ALTER DATABASE dn1 SET timescaledb.rest
docker_exec ${CONTAINER_CLEAN_RESTORE} "pg_restore -h localhost -U postgres -d dn1 /tmp/dn1.sql"
docker_pgcmd ${CONTAINER_CLEAN_RESTORE} "ALTER DATABASE dn1 SET timescaledb.restoring='off'"
echo "Comparing upgraded ($UPDATE_FROM_TAG -> $UPDATE_TO_TAG) with clean install ($UPDATE_TO_TAG)"
docker_pgdiff_all /src/test/sql/updates/post.${TEST_VERSION}.sql "single"

View File

@ -0,0 +1,38 @@
-- For continuous aggregates: Copy ACL privileges (grants) from the
-- query view (user-facing object) to the internal objects (e.g.,
-- materialized hypertable, direct, and partial views). We want to
-- maintain the abstraction that a continuous aggregates is similar to
-- a materialized view (which is one object), so privileges on the
-- user-facing object should apply also to the internal objects that
-- implement the continuous aggregate. Having the right permissions on
-- internal objects is necessary for the watermark function used by
-- real-time aggregation since it queries the materialized hypertable
-- directly.
WITH rels_and_acl AS (
-- For each cagg, collect an array of all relations (including
-- chunks) to copy the ACL to
SELECT array_cat(ARRAY[format('%I.%I', h.schema_name, h.table_name)::regclass,
format('%I.%I', direct_view_schema, direct_view_name)::regclass,
format('%I.%I', partial_view_schema, partial_view_name)::regclass],
(SELECT array_agg(inhrelid::regclass)
FROM pg_inherits
WHERE inhparent = format('%I.%I', h.schema_name, h.table_name)::regclass)) AS relarr,
relacl AS user_view_acl
FROM _timescaledb_catalog.continuous_agg ca
LEFT JOIN pg_class cl
ON (cl.oid = format('%I.%I', user_view_schema, user_view_name)::regclass)
LEFT JOIN _timescaledb_catalog.hypertable h
ON (ca.mat_hypertable_id = h.id)
WHERE relacl IS NOT NULL
)
-- Set the ACL on all internal cagg relations, including
-- chunks. Note that we cannot use GRANT statements because
-- such statements are recorded as privileges on extension
-- objects when run in an update script. The result is that
-- the privileges will become init privileges, which will then
-- be ignored by, e.g., pg_dump.
UPDATE pg_class
SET relacl = user_view_acl
FROM rels_and_acl
WHERE oid = ANY (relarr);

View File

@ -17,3 +17,72 @@ CALL refresh_continuous_aggregate('mat_before',NULL,NULL);
--the max of the temp for the POR should now be 165
SELECT * FROM mat_before ORDER BY bucket, location;
SET ROLE cagg_user;
--should be able to query as cagg_user
SELECT * FROM mat_before ORDER BY bucket, location;
RESET ROLE;
-- Output the ACLs for each internal cagg object
SELECT cl.oid::regclass::text AS reloid,
relacl
FROM _timescaledb_catalog.continuous_agg ca
JOIN _timescaledb_catalog.hypertable h
ON (ca.mat_hypertable_id = h.id)
JOIN pg_class cl
ON (cl.oid IN (format('%I.%I', h.schema_name, h.table_name)::regclass,
format('%I.%I', direct_view_schema, direct_view_name)::regclass,
format('%I.%I', partial_view_schema, partial_view_name)::regclass))
ORDER BY reloid;
-- Output ACLs for chunks on materialized hypertables
SELECT inhparent::regclass::text AS parent,
cl.oid::regclass::text AS chunk,
relacl
FROM _timescaledb_catalog.continuous_agg ca
JOIN _timescaledb_catalog.hypertable h
ON (ca.mat_hypertable_id = h.id)
JOIN pg_inherits inh ON (inh.inhparent = format('%I.%I', h.schema_name, h.table_name)::regclass)
JOIN pg_class cl
ON (cl.oid = inh.inhrelid)
ORDER BY parent, chunk;
-- Verify privileges on internal cagg objects. The privileges on the
-- materialized hypertable, partial view, and direct view should match
-- the user-facing user view.
DO $$
DECLARE
user_view_rel regclass;
user_view_acl aclitem[];
rel regclass;
acl aclitem[];
acl_matches boolean;
BEGIN
FOR user_view_rel, user_view_acl IN
SELECT cl.oid, cl.relacl
FROM pg_class cl
JOIN _timescaledb_catalog.continuous_agg ca
ON (format('%I.%I', ca.user_view_schema, ca.user_view_name)::regclass = cl.oid)
LOOP
FOR rel, acl, acl_matches IN
SELECT cl.oid,
cl.relacl,
COALESCE(cl.relacl, ARRAY[]::aclitem[]) @> COALESCE(user_view_acl, ARRAY[]::aclitem[])
FROM _timescaledb_catalog.continuous_agg ca
JOIN _timescaledb_catalog.hypertable h
ON (ca.mat_hypertable_id = h.id)
JOIN pg_class cl
ON (cl.oid IN (format('%I.%I', h.schema_name, h.table_name)::regclass,
format('%I.%I', direct_view_schema, direct_view_name)::regclass,
format('%I.%I', partial_view_schema, partial_view_name)::regclass))
WHERE format('%I.%I', ca.user_view_schema, ca.user_view_name)::regclass = user_view_rel
LOOP
IF NOT acl_matches THEN
RAISE EXCEPTION 'privileges mismatch for continuous aggregate "%"', user_view_rel
USING DETAIL = format('Privileges for internal object "%s" are [%s], expected [%s].',
rel, acl, user_view_acl);
END IF;
END LOOP;
END LOOP;
END
$$ LANGUAGE PLPGSQL;

View File

@ -130,6 +130,8 @@ BEGIN
END IF;
END $$;
GRANT SELECT ON mat_before TO cagg_user WITH GRANT OPTION;
-- have to use psql conditional here because the procedure call can't be in transaction
SELECT extversion < '2.0.0' AS has_refresh_mat_view from pg_extension WHERE extname = 'timescaledb' \gset
\if :has_refresh_mat_view

View File

@ -49,7 +49,6 @@ DECLARE
ts_version TEXT;
BEGIN
SELECT extversion INTO ts_version FROM pg_extension WHERE extname = 'timescaledb';
IF ts_version < '2.0.0' THEN
CREATE VIEW mat_before
WITH ( timescaledb.continuous, timescaledb.materialized_only=true, timescaledb.refresh_lag='-30 day', timescaledb.max_interval_per_job ='1000 day')
@ -143,6 +142,8 @@ BEGIN
END IF;
END $$;
GRANT SELECT ON mat_before TO cagg_user WITH GRANT OPTION;
\if :has_refresh_mat_view
REFRESH MATERIALIZED VIEW mat_before;
\else
@ -251,6 +252,8 @@ BEGIN
END IF;
END $$;
GRANT SELECT ON cagg.realtime_mat TO cagg_user;
\if :has_refresh_mat_view
REFRESH MATERIALIZED VIEW cagg.realtime_mat;
\else
@ -450,6 +453,10 @@ BEGIN
END IF;
END $$;
GRANT SELECT, TRIGGER, UPDATE
ON mat_conflict TO cagg_user
WITH GRANT OPTION;
-- Test that calling drop chunks on the hypertable does not break the
-- update process when chunks are marked as dropped rather than
-- removed. This happens when a continuous aggregate is defined on the

View File

@ -0,0 +1,5 @@
-- This file and its contents are licensed under the Apache License 2.0.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.
CREATE ROLE cagg_user;