From 4d355a8336e0f2265b31d678ffd1ee5cf9e79fae Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Mon, 27 Sep 2010 20:55:27 -0400 Subject: [PATCH] Add a SECURITY LABEL command. This is intended as infrastructure to support integration with label-based mandatory access control systems such as SE-Linux. Further changes (mostly hooks) will be needed, but this is a big chunk of it. KaiGai Kohei and Robert Haas --- contrib/Makefile | 1 + contrib/dummy_seclabel/Makefile | 14 + contrib/dummy_seclabel/dummy_seclabel.c | 49 ++++ doc/src/sgml/catalogs.sgml | 172 ++++++++++++ doc/src/sgml/ref/allfiles.sgml | 1 + doc/src/sgml/ref/pg_dump.sgml | 10 + doc/src/sgml/ref/pg_dumpall.sgml | 9 + doc/src/sgml/ref/pg_restore.sgml | 10 + doc/src/sgml/ref/security_label.sgml | 194 +++++++++++++ doc/src/sgml/reference.sgml | 1 + src/backend/catalog/Makefile | 2 +- src/backend/catalog/dependency.c | 7 +- src/backend/catalog/system_views.sql | 108 +++++++ src/backend/commands/Makefile | 2 +- src/backend/commands/seclabel.c | 387 ++++++++++++++++++++++++++ src/backend/nodes/copyfuncs.c | 17 ++ src/backend/nodes/equalfuncs.c | 15 + src/backend/parser/gram.y | 96 ++++++- src/backend/tcop/utility.c | 14 + src/bin/pg_dump/pg_backup.h | 1 + src/bin/pg_dump/pg_backup_archiver.c | 6 + src/bin/pg_dump/pg_dump.c | 370 +++++++++++++++++++++++- src/bin/pg_dump/pg_dumpall.c | 7 + src/bin/pg_dump/pg_restore.c | 6 + src/bin/psql/tab-complete.c | 39 ++- src/include/catalog/catversion.h | 2 +- src/include/catalog/indexing.h | 3 + src/include/catalog/pg_seclabel.h | 43 +++ src/include/catalog/toasting.h | 1 + src/include/commands/seclabel.h | 35 +++ src/include/nodes/nodes.h | 1 + src/include/nodes/parsenodes.h | 14 + src/include/parser/kwlist.h | 1 + src/test/regress/GNUmakefile | 12 +- src/test/regress/expected/.gitignore | 1 + src/test/regress/expected/rules.out | 7 +- src/test/regress/expected/sanity_check.out | 3 +- src/test/regress/input/security_label.source | 84 ++++++ src/test/regress/output/security_label.source | 92 ++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/serial_schedule | 1 + src/test/regress/sql/.gitignore | 1 + 42 files changed, 1815 insertions(+), 26 deletions(-) create mode 100644 contrib/dummy_seclabel/Makefile create mode 100644 contrib/dummy_seclabel/dummy_seclabel.c create mode 100644 doc/src/sgml/ref/security_label.sgml create mode 100644 src/backend/commands/seclabel.c create mode 100644 src/include/catalog/pg_seclabel.h create mode 100644 src/include/commands/seclabel.h create mode 100644 src/test/regress/input/security_label.source create mode 100644 src/test/regress/output/security_label.source diff --git a/contrib/Makefile b/contrib/Makefile index c1d3317c2d..b777325534 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -15,6 +15,7 @@ SUBDIRS = \ dblink \ dict_int \ dict_xsyn \ + dummy_seclabel \ earthdistance \ fuzzystrmatch \ hstore \ diff --git a/contrib/dummy_seclabel/Makefile b/contrib/dummy_seclabel/Makefile new file mode 100644 index 0000000000..105400f5f9 --- /dev/null +++ b/contrib/dummy_seclabel/Makefile @@ -0,0 +1,14 @@ +# contrib/dummy_seclabel/Makefile + +MODULES = dummy_seclabel + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/dummy_seclabel +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/dummy_seclabel/dummy_seclabel.c b/contrib/dummy_seclabel/dummy_seclabel.c new file mode 100644 index 0000000000..8bd50a34cf --- /dev/null +++ b/contrib/dummy_seclabel/dummy_seclabel.c @@ -0,0 +1,49 @@ +/* + * dummy_seclabel.c + * + * Dummy security label provider. + * + * This module does not provide anything worthwhile from a security + * perspective, but allows regression testing independent of platform-specific + * features like SELinux. + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#include "postgres.h" + +#include "commands/seclabel.h" +#include "miscadmin.h" + +PG_MODULE_MAGIC; + +/* Entrypoint of the module */ +void _PG_init(void); + +static void +dummy_object_relabel(const ObjectAddress *object, const char *seclabel) +{ + if (seclabel == NULL || + strcmp(seclabel, "unclassified") == 0 || + strcmp(seclabel, "classified") == 0) + return; + + if (strcmp(seclabel, "secret") == 0 || + strcmp(seclabel, "top secret") == 0) + { + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("only superuser can set '%s' label", seclabel))); + return; + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_NAME), + errmsg("'%s' is not a valid security label", seclabel))); +} + +void +_PG_init(void) +{ + register_label_provider("dummy", dummy_object_relabel); +} diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index ab11b15065..8e4081cb33 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -209,6 +209,11 @@ + pg_seclabel + security labels on database objects + + + pg_shdepend dependencies on shared objects @@ -4229,6 +4234,77 @@ + + <structname>pg_seclabel</structname> + + + pg_seclabel + + + + The catalog pg_seclabel stores security + labels on database objects. See the + statement. + + + + <structname>pg_seclabel</structname> Columns + + + + + Name + Type + References + Description + + + + + + objoid + oid + any OID column + The OID of the object this security label pertains to + + + + classoid + oid + pg_class.oid + The OID of the system catalog this object appears in + + + + objsubid + int4 + + + For a security label on a table column, this is the column number (the + objoid and classoid refer to + the table itself). For all other object types, this column is + zero. + + + + + provider + text + + The label provider associated with this label. + + + + label + text + + The security label applied to this object. + + + +
+
+ <structname>pg_shdepend</structname> @@ -5884,6 +5960,11 @@ + pg_seclabels + security labels + + + pg_settings parameter settings @@ -6791,6 +6872,97 @@ + + <structname>pg_seclabels</structname> + + + pg_seclabels + + + + The view pg_seclabels provides information about + security labels. It as an easier-to-query version of the + pg_seclabel catalog. + + + + <structname>pg_seclabels</> Columns + + + + + Name + Type + References + Description + + + + + objoid + oid + any OID column + The OID of the object this security label pertains to + + + classoid + oid + pg_class.oid + The OID of the system catalog this object appears in + + + objsubid + int4 + + + For a security label on a table column, this is the column number (the + objoid and classoid refer to + the table itself). For all other object types, this column is + zero. + + + + objtype + text + + + The type of object to which this label applies, as text. + + + + objnamespace + oid + pg_namespace.oid + + The OID of the namespace for this object, if applicable; + otherwise NULL. + + + + objname + text + + + The name of the object to which this label applies, as text. + + + + provider + text + pg_seclabel.provider + The label provider associated with this label. + + + label + text + pg_seclabel.label + The security label applied to this object. + + + +
+
+ <structname>pg_settings</structname> diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index 7b97883d1b..f5d67a2078 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -132,6 +132,7 @@ Complete list of usable sgml source files in this directory. + diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index 1b8402e78c..8242b536d7 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -778,6 +778,16 @@ PostgreSQL documentation + + + + + + With this option, it also outputs security labels of database + objects to be dumped, if labeled. + + + diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml index 14fa109112..68dcc35c50 100644 --- a/doc/src/sgml/ref/pg_dumpall.sgml +++ b/doc/src/sgml/ref/pg_dumpall.sgml @@ -493,6 +493,15 @@ PostgreSQL documentation + + + + + With this option, it also outputs security labels of database + objects to be dumped, if labeled. + + + diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml index 9dc2511f5f..78606969a9 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -329,6 +329,16 @@ + + + + Do not output commands to restore security labels, + even if the archive contains them. + + + + + diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml new file mode 100644 index 0000000000..7fce58bc13 --- /dev/null +++ b/doc/src/sgml/ref/security_label.sgml @@ -0,0 +1,194 @@ + + + + + SECURITY LABEL + 7 + SQL - Language Statements + + + + SECURITY LABEL + define or change a security label applied to an object + + + + SECURITY LABEL + + + + +SECURITY LABEL [ FOR provider ] ON +{ + TABLE object_name | + COLUMN table_name.column_name | + AGGREGATE agg_name (agg_type [, ...] ) | + DOMAIN object_name | + FUNCTION function_name ( [ [ argmode ] [ argname ] argtype [, ...] ] ) | + LARGE OBJECT large_object_oid | + [ PROCEDURAL ] LANGUAGE object_name | + SCHEMA object_name | + SEQUENCE object_name | + TYPE object_name | + VIEW object_name +} IS 'label' + + + + + Description + + + SECURITY LABEL applies a security label to a database + object. An arbitrary number of security labels, one per label provider, can + be associated with a given database object. Label providers are loadable + modules which register themselves by using the function + register_label_provider. + + + + + register_label_provider is not an SQL function; it can + only be called from C code loaded into the backend. + + + + + The label provider determines whether a given a label is valid and whether + it is permissible to assign that label to a given object. The meaning of a + given label is likewise at the discretion of the label provider. + PostgreSQL places no restrictions on whether or how a + label provider must interpret security labels; it merely provides a + mechanism for storing them. In practice, this facility is intended to allow + integration with label-based mandatory access control (MAC) systems such as + SE-Linux. Such systems make all access control decisions + based on object labels, rather than traditional discretionary access control + (DAC) concepts such as users and groups. + + + + + Parameters + + + + object_name + table_name.column_name + agg_name + function_name + + + The name of the object to be commented. Names of tables, + aggregates, domains, functions, sequences, types, and views can + be schema-qualified. + + + + + + provider + + + The name of the provider with which this label is to be associated. The + named provider must be loaded and must consent to the proposed labeling + operation. If exactly one provider is loaded, the provider name may be + omitted for brevity. + + + + + + argmode + + + + The mode of a function argument: IN, OUT, + INOUT, or VARIADIC. + If omitted, the default is IN. + Note that COMMENT ON FUNCTION does not actually pay + any attention to OUT arguments, since only the input + arguments are needed to determine the function's identity. + So it is sufficient to list the IN, INOUT, + and VARIADIC arguments. + + + + + + argname + + + + The name of a function argument. + Note that COMMENT ON FUNCTION does not actually pay + any attention to argument names, since only the argument data + types are needed to determine the function's identity. + + + + + + argtype + + + + The data type(s) of the function's arguments (optionally + schema-qualified), if any. + + + + + + large_object_oid + + + The OID of the large object. + + + + + + PROCEDURAL + + + + This is a noise word. + + + + + + label + + + The new security label, written as a string literal; or NULL + to drop the security label. + + + + + + + + Examples + + + The following example shows how the security label of a table might + be changed. + + +SECURITY LABEL FOR selinux ON TABLE mytable IS 'system_u:object_r:sepgsql_table_t:s0'; + + + + + + Compatibility + + There is no SECURITY LABEL command in the SQL standard. + + + diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index 052fe0e8fb..463746cda3 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -160,6 +160,7 @@ &rollbackPrepared; &rollbackTo; &savepoint; + &securityLabel; &select; &selectInto; &set; diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index f4a7eb09dc..6a47f398ed 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -38,7 +38,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \ pg_ts_parser.h pg_ts_template.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ - pg_default_acl.h \ + pg_default_acl.h pg_seclabel.h \ toasting.h indexing.h \ ) diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 62598ee8f8..18e07eb956 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -57,6 +57,7 @@ #include "commands/defrem.h" #include "commands/proclang.h" #include "commands/schemacmds.h" +#include "commands/seclabel.h" #include "commands/tablespace.h" #include "commands/trigger.h" #include "commands/typecmds.h" @@ -1004,10 +1005,12 @@ deleteOneObject(const ObjectAddress *object, Relation depRel) doDeletion(object); /* - * Delete any comments associated with this object. (This is a convenient - * place to do it instead of having every object type know to do it.) + * Delete any comments or security labels associated with this object. + * (This is a convenient place to do these things, rather than having every + * object type know to do it.) */ DeleteComments(object->objectId, object->classId, object->objectSubId); + DeleteSecurityLabel(object); /* * CommandCounterIncrement here to ensure that preceding changes are all diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 651ffc61b9..09574c3e82 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -160,6 +160,114 @@ CREATE VIEW pg_prepared_xacts AS CREATE VIEW pg_prepared_statements AS SELECT * FROM pg_prepared_statement() AS P; +CREATE VIEW pg_seclabels AS +SELECT + l.objoid, l.classoid, l.objsubid, + CASE WHEN rel.relkind = 'r' THEN 'table'::text + WHEN rel.relkind = 'v' THEN 'view'::text + WHEN rel.relkind = 'S' THEN 'sequence'::text END AS objtype, + rel.relnamespace AS objnamespace, + CASE WHEN pg_table_is_visible(rel.oid) + THEN quote_ident(rel.relname) + ELSE quote_ident(nsp.nspname) || '.' || quote_ident(rel.relname) + END AS objname, + l.provider, l.label +FROM + pg_seclabel l + JOIN pg_class rel ON l.classoid = rel.tableoid AND l.objoid = rel.oid + JOIN pg_namespace nsp ON rel.relnamespace = nsp.oid +WHERE + l.objsubid = 0 +UNION ALL +SELECT + l.objoid, l.classoid, l.objsubid, + 'column'::text AS objtype, + rel.relnamespace AS objnamespace, + CASE WHEN pg_table_is_visible(rel.oid) + THEN quote_ident(rel.relname) + ELSE quote_ident(nsp.nspname) || '.' || quote_ident(rel.relname) + END || '.' || att.attname AS objname, + l.provider, l.label +FROM + pg_seclabel l + JOIN pg_class rel ON l.classoid = rel.tableoid AND l.objoid = rel.oid + JOIN pg_attribute att + ON rel.oid = att.attrelid AND l.objsubid = att.attnum + JOIN pg_namespace nsp ON rel.relnamespace = nsp.oid +WHERE + l.objsubid != 0 +UNION ALL +SELECT + l.objoid, l.classoid, l.objsubid, + CASE WHEN pro.proisagg = true THEN 'aggregate'::text + WHEN pro.proisagg = false THEN 'function'::text + END AS objtype, + pro.pronamespace AS objnamespace, + CASE WHEN pg_function_is_visible(pro.oid) + THEN quote_ident(pro.proname) + ELSE quote_ident(nsp.nspname) || '.' || quote_ident(pro.proname) + END || '(' || pg_catalog.pg_get_function_arguments(pro.oid) || ')' AS objname, + l.provider, l.label +FROM + pg_seclabel l + JOIN pg_proc pro ON l.classoid = pro.tableoid AND l.objoid = pro.oid + JOIN pg_namespace nsp ON pro.pronamespace = nsp.oid +WHERE + l.objsubid = 0 +UNION ALL +SELECT + l.objoid, l.classoid, l.objsubid, + CASE WHEN typ.typtype = 'd' THEN 'domain'::text + ELSE 'type'::text END AS objtype, + typ.typnamespace AS objnamespace, + CASE WHEN pg_type_is_visible(typ.oid) + THEN quote_ident(typ.typname) + ELSE quote_ident(nsp.nspname) || '.' || quote_ident(typ.typname) + END AS objname, + l.provider, l.label +FROM + pg_seclabel l + JOIN pg_type typ ON l.classoid = typ.tableoid AND l.objoid = typ.oid + JOIN pg_namespace nsp ON typ.typnamespace = nsp.oid +WHERE + l.objsubid = 0 +UNION ALL +SELECT + l.objoid, l.classoid, l.objsubid, + 'large object'::text AS objtype, + NULL::oid AS objnamespace, + l.objoid::text AS objname, + l.provider, l.label +FROM + pg_seclabel l + JOIN pg_largeobject_metadata lom ON l.objoid = lom.oid +WHERE + l.classoid = 'pg_catalog.pg_largeobject'::regclass AND l.objsubid = 0 +UNION ALL +SELECT + l.objoid, l.classoid, l.objsubid, + 'language'::text AS objtype, + NULL::oid AS objnamespace, + quote_ident(lan.lanname) AS objname, + l.provider, l.label +FROM + pg_seclabel l + JOIN pg_language lan ON l.classoid = lan.tableoid AND l.objoid = lan.oid +WHERE + l.objsubid = 0 +UNION ALL +SELECT + l.objoid, l.classoid, l.objsubid, + 'schema'::text AS objtype, + nsp.oid AS objnamespace, + quote_ident(nsp.nspname) AS objname, + l.provider, l.label +FROM + pg_seclabel l + JOIN pg_namespace nsp ON l.classoid = nsp.tableoid AND l.objoid = nsp.oid +WHERE + l.objsubid = 0; + CREATE VIEW pg_settings AS SELECT * FROM pg_show_all_settings() AS A; diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index 4e9bf43ad5..9d2a732245 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -17,7 +17,7 @@ OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \ dbcommands.o define.o discard.o explain.o foreigncmds.o functioncmds.o \ indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \ portalcmds.o prepare.o proclang.o \ - schemacmds.o sequence.o tablecmds.o tablespace.o trigger.o \ + schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \ tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \ variable.o view.o diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c new file mode 100644 index 0000000000..417ad88d55 --- /dev/null +++ b/src/backend/commands/seclabel.c @@ -0,0 +1,387 @@ +/* ------------------------------------------------------------------------- + * + * seclabel.c + * routines to support security label feature. + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "catalog/catalog.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/pg_seclabel.h" +#include "commands/seclabel.h" +#include "miscadmin.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/tqual.h" + +/* + * For most object types, the permissions-checking logic is simple enough + * that it makes sense to just include it in CommentObject(). However, + * attributes require a bit more checking. + */ +static void CheckAttributeSecLabel(Relation relation); + +typedef struct +{ + const char *provider_name; + check_object_relabel_type hook; +} LabelProvider; + +static List *label_provider_list = NIL; + +/* + * ExecSecLabelStmt -- + * + * Apply a security label to a database object. + */ +void +ExecSecLabelStmt(SecLabelStmt *stmt) +{ + LabelProvider *provider = NULL; + ObjectAddress address; + Relation relation; + ListCell *lc; + + /* + * Find the named label provider, or if none specified, check whether + * there's exactly one, and if so use it. + */ + if (stmt->provider == NULL) + { + if (label_provider_list == NIL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("security label providers have been loaded"))); + if (lnext(list_head(label_provider_list)) != NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("must specify provider when multiple security label providers have been loaded"))); + provider = (LabelProvider *) linitial(label_provider_list); + } + else + { + foreach (lc, label_provider_list) + { + LabelProvider *lp = lfirst(lc); + + if (strcmp(stmt->provider, lp->provider_name) == 0) + { + provider = lp; + break; + } + } + if (provider == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("security label provider \"%s\" is not loaded", + stmt->provider))); + } + + /* + * Translate the parser representation which identifies this object + * into an ObjectAddress. get_object_address() will throw an error if + * the object does not exist, and will also acquire a lock on the + * target to guard against concurrent modifications. + */ + address = get_object_address(stmt->objtype, stmt->objname, stmt->objargs, + &relation, ShareUpdateExclusiveLock); + + /* Privilege and integrity checks. */ + switch (stmt->objtype) + { + case OBJECT_SEQUENCE: + case OBJECT_TABLE: + case OBJECT_VIEW: + if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, + RelationGetRelationName(relation)); + break; + case OBJECT_COLUMN: + CheckAttributeSecLabel(relation); + break; + case OBJECT_TYPE: + if (!pg_type_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE, + format_type_be(address.objectId)); + break; + case OBJECT_AGGREGATE: + case OBJECT_FUNCTION: + if (!pg_proc_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, + NameListToString(stmt->objname)); + break; + case OBJECT_SCHEMA: + if (!pg_namespace_ownercheck(address.objectId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE, + strVal(linitial(stmt->objname))); + break; + case OBJECT_LANGUAGE: + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to comment on procedural language"))); + break; + case OBJECT_LARGEOBJECT: + if (!pg_largeobject_ownercheck(address.objectId, GetUserId())) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be owner of large object %u", + address.objectId))); + break; + default: + elog(ERROR, "unrecognized object type: %d", + (int) stmt->objtype); + } + + /* Provider gets control here, may throw ERROR to veto new label. */ + (*provider->hook)(&address, stmt->label); + + /* Apply new label. */ + SetSecurityLabel(&address, provider->provider_name, stmt->label); + + /* + * If get_object_address() opened the relation for us, we close it to keep + * the reference count correct - but we retain any locks acquired by + * get_object_address() until commit time, to guard against concurrent + * activity. + */ + if (relation != NULL) + relation_close(relation, NoLock); +} + +/* + * GetSecurityLabel returns the security label for a database object for a + * given provider, or NULL if there is no such label. + */ +char * +GetSecurityLabel(const ObjectAddress *object, const char *provider) +{ + Relation pg_seclabel; + ScanKeyData keys[4]; + SysScanDesc scan; + HeapTuple tuple; + Datum datum; + bool isnull; + char *seclabel = NULL; + + Assert(!IsSharedRelation(object->classId)); + + ScanKeyInit(&keys[0], + Anum_pg_seclabel_objoid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + ScanKeyInit(&keys[1], + Anum_pg_seclabel_classoid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->classId)); + ScanKeyInit(&keys[2], + Anum_pg_seclabel_objsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(object->objectSubId)); + ScanKeyInit(&keys[3], + Anum_pg_seclabel_provider, + BTEqualStrategyNumber, F_TEXTEQ, + CStringGetTextDatum(provider)); + + pg_seclabel = heap_open(SecLabelRelationId, AccessShareLock); + + scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true, + SnapshotNow, 4, keys); + + tuple = systable_getnext(scan); + if (HeapTupleIsValid(tuple)) + { + datum = heap_getattr(tuple, Anum_pg_seclabel_label, + RelationGetDescr(pg_seclabel), &isnull); + if (!isnull) + seclabel = TextDatumGetCString(datum); + } + systable_endscan(scan); + + heap_close(pg_seclabel, AccessShareLock); + + return seclabel; +} + +/* + * SetSecurityLabel attempts to set the security label for the specified + * provider on the specified object to the given value. NULL means that any + * any existing label should be deleted. + */ +void +SetSecurityLabel(const ObjectAddress *object, + const char *provider, const char *label) +{ + Relation pg_seclabel; + ScanKeyData keys[4]; + SysScanDesc scan; + HeapTuple oldtup; + HeapTuple newtup = NULL; + Datum values[Natts_pg_seclabel]; + bool nulls[Natts_pg_seclabel]; + bool replaces[Natts_pg_seclabel]; + + /* Security labels on shared objects are not supported. */ + Assert(!IsSharedRelation(object->classId)); + + /* Prepare to form or update a tuple, if necessary. */ + memset(nulls, false, sizeof(nulls)); + memset(replaces, false, sizeof(replaces)); + values[Anum_pg_seclabel_objoid - 1] = ObjectIdGetDatum(object->objectId); + values[Anum_pg_seclabel_classoid - 1] = ObjectIdGetDatum(object->classId); + values[Anum_pg_seclabel_objsubid - 1] = Int32GetDatum(object->objectSubId); + values[Anum_pg_seclabel_provider - 1] = CStringGetTextDatum(provider); + if (label != NULL) + values[Anum_pg_seclabel_label - 1] = CStringGetTextDatum(label); + + /* Use the index to search for a matching old tuple */ + ScanKeyInit(&keys[0], + Anum_pg_seclabel_objoid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + ScanKeyInit(&keys[1], + Anum_pg_seclabel_classoid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->classId)); + ScanKeyInit(&keys[2], + Anum_pg_seclabel_objsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(object->objectSubId)); + ScanKeyInit(&keys[3], + Anum_pg_seclabel_provider, + BTEqualStrategyNumber, F_TEXTEQ, + CStringGetTextDatum(provider)); + + pg_seclabel = heap_open(SecLabelRelationId, RowExclusiveLock); + + scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true, + SnapshotNow, 4, keys); + + oldtup = systable_getnext(scan); + if (HeapTupleIsValid(oldtup)) + { + if (label == NULL) + simple_heap_delete(pg_seclabel, &oldtup->t_self); + else + { + replaces[Anum_pg_seclabel_label - 1] = true; + newtup = heap_modify_tuple(oldtup, RelationGetDescr(pg_seclabel), + values, nulls, replaces); + simple_heap_update(pg_seclabel, &oldtup->t_self, newtup); + } + } + systable_endscan(scan); + + /* If we didn't find an old tuple, insert a new one */ + if (newtup == NULL && label != NULL) + { + newtup = heap_form_tuple(RelationGetDescr(pg_seclabel), + values, nulls); + simple_heap_insert(pg_seclabel, newtup); + } + + /* Update indexes, if necessary */ + if (newtup != NULL) + { + CatalogUpdateIndexes(pg_seclabel, newtup); + heap_freetuple(newtup); + } + + heap_close(pg_seclabel, RowExclusiveLock); +} + +/* + * DeleteSecurityLabel removes all security labels for an object (and any + * sub-objects, if applicable). + */ +void +DeleteSecurityLabel(const ObjectAddress *object) +{ + Relation pg_seclabel; + ScanKeyData skey[3]; + SysScanDesc scan; + HeapTuple oldtup; + int nkeys; + + /* Security labels on shared objects are not supported. */ + if (IsSharedRelation(object->classId)) + return; + + ScanKeyInit(&skey[0], + Anum_pg_seclabel_objoid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + ScanKeyInit(&skey[1], + Anum_pg_seclabel_classoid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->classId)); + if (object->objectSubId != 0) + { + ScanKeyInit(&skey[2], + Anum_pg_seclabel_objsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(object->objectSubId)); + nkeys = 3; + } + else + nkeys = 2; + + pg_seclabel = heap_open(SecLabelRelationId, RowExclusiveLock); + + scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true, + SnapshotNow, nkeys, skey); + while (HeapTupleIsValid(oldtup = systable_getnext(scan))) + simple_heap_delete(pg_seclabel, &oldtup->t_self); + systable_endscan(scan); + + heap_close(pg_seclabel, RowExclusiveLock); +} + +/* + * Check whether the user is allowed to comment on an attribute of the + * specified relation. + */ +static void +CheckAttributeSecLabel(Relation relation) +{ + if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, + RelationGetRelationName(relation)); + + /* + * Allow security labels only on columns of tables, views, and composite + * types (which are the only relkinds for which pg_dump will dump labels). + */ + if (relation->rd_rel->relkind != RELKIND_RELATION && + relation->rd_rel->relkind != RELKIND_VIEW && + relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table, view, or composite type", + RelationGetRelationName(relation)))); +} + +void +register_label_provider(const char *provider_name, check_object_relabel_type hook) +{ + LabelProvider *provider; + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + provider = palloc(sizeof(LabelProvider)); + provider->provider_name = pstrdup(provider_name); + provider->hook = hook; + label_provider_list = lappend(label_provider_list, provider); + MemoryContextSwitchTo(oldcxt); +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index deaeb761d4..e07aa3ead2 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2607,6 +2607,20 @@ _copyCommentStmt(CommentStmt *from) return newnode; } +static SecLabelStmt * +_copySecLabelStmt(SecLabelStmt *from) +{ + SecLabelStmt *newnode = makeNode(SecLabelStmt); + + COPY_SCALAR_FIELD(objtype); + COPY_NODE_FIELD(objname); + COPY_NODE_FIELD(objargs); + COPY_STRING_FIELD(provider); + COPY_STRING_FIELD(label); + + return newnode; +} + static FetchStmt * _copyFetchStmt(FetchStmt *from) { @@ -3958,6 +3972,9 @@ copyObject(void *from) case T_CommentStmt: retval = _copyCommentStmt(from); break; + case T_SecLabelStmt: + retval = _copySecLabelStmt(from); + break; case T_FetchStmt: retval = _copyFetchStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 6b6cd9966c..8d083c8796 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1164,6 +1164,18 @@ _equalCommentStmt(CommentStmt *a, CommentStmt *b) } static bool +_equalSecLabelStmt(SecLabelStmt *a, SecLabelStmt *b) +{ + COMPARE_SCALAR_FIELD(objtype); + COMPARE_NODE_FIELD(objname); + COMPARE_NODE_FIELD(objargs); + COMPARE_STRING_FIELD(provider); + COMPARE_STRING_FIELD(label); + + return true; +} + +static bool _equalFetchStmt(FetchStmt *a, FetchStmt *b) { COMPARE_SCALAR_FIELD(direction); @@ -2624,6 +2636,9 @@ equal(void *a, void *b) case T_CommentStmt: retval = _equalCommentStmt(a, b); break; + case T_SecLabelStmt: + retval = _equalSecLabelStmt(a, b); + break; case T_FetchStmt: retval = _equalFetchStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 40bd7a3932..4054cb1bc7 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -205,7 +205,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt RuleActionStmt RuleActionStmtOrEmpty RuleStmt - SelectStmt TransactionStmt TruncateStmt + SecLabelStmt SelectStmt TransactionStmt TruncateStmt UnlistenStmt UpdateStmt VacuumStmt VariableResetStmt VariableSetStmt VariableShowStmt ViewStmt CheckPointStmt CreateConversionStmt @@ -335,7 +335,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ %type copy_from %type opt_column event cursor_options opt_hold opt_set_data -%type reindex_type drop_type comment_type +%type reindex_type drop_type comment_type security_label_type %type fetch_args limit_clause select_limit_value offset_clause select_offset_value @@ -423,6 +423,8 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ %type OptTableSpace OptConsTableSpace OptTableSpaceOwner %type opt_check_option +%type opt_provider security_label + %type xml_attribute_el %type xml_attribute_list xml_attributes %type xml_root_version opt_xml_root_standalone @@ -500,7 +502,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ KEY - LANGUAGE LARGE_P LAST_P LC_COLLATE_P LC_CTYPE_P LEADING + LABEL LANGUAGE LARGE_P LAST_P LC_COLLATE_P LC_CTYPE_P LEADING LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOGIN_P @@ -739,6 +741,7 @@ stmt : | RevokeStmt | RevokeRoleStmt | RuleStmt + | SecLabelStmt | SelectStmt | TransactionStmt | TruncateStmt @@ -4368,6 +4371,92 @@ comment_text: | NULL_P { $$ = NULL; } ; + +/***************************************************************************** + * + * SECURITY LABEL [FOR ] ON IS