diff options
author | Robert Haas | 2010-09-28 00:55:27 +0000 |
---|---|---|
committer | Robert Haas | 2010-09-28 00:55:27 +0000 |
commit | 4d355a8336e0f2265b31d678ffd1ee5cf9e79fae (patch) | |
tree | 9ab7e59c81ca1a8417ea2bfe8b3c11e232f3a9ee | |
parent | 2ce003973db82205cec55d596d51e957293019d1 (diff) |
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
42 files changed, 1815 insertions, 26 deletions
diff --git a/contrib/Makefile b/contrib/Makefile index c1d3317c2d5..b7773255341 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 00000000000..105400f5f98 --- /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 00000000000..8bd50a34cfc --- /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 ab11b150653..8e4081cb33c 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -209,6 +209,11 @@ </row> <row> + <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry> + <entry>security labels on database objects</entry> + </row> + + <row> <entry><link linkend="catalog-pg-shdepend"><structname>pg_shdepend</structname></link></entry> <entry>dependencies on shared objects</entry> </row> @@ -4229,6 +4234,77 @@ </sect1> + <sect1 id="catalog-pg-seclabel"> + <title><structname>pg_seclabel</structname></title> + + <indexterm zone="catalog-pg-seclabel"> + <primary>pg_seclabel</primary> + </indexterm> + + <para> + The catalog <structname>pg_seclabel</structname> stores security + labels on database objects. See the + <xref linkend="sql-security-label"> statement. + </para> + + <table> + <title><structname>pg_seclabel</structname> Columns</title> + + <tgroup cols="4"> + <thead> + <row> + <entry>Name</entry> + <entry>Type</entry> + <entry>References</entry> + <entry>Description</entry> + </row> + </thead> + + <tbody> + <row> + <entry><structfield>objoid</structfield></entry> + <entry><type>oid</type></entry> + <entry>any OID column</entry> + <entry>The OID of the object this security label pertains to</entry> + </row> + + <row> + <entry><structfield>classoid</structfield></entry> + <entry><type>oid</type></entry> + <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry> + <entry>The OID of the system catalog this object appears in</entry> + </row> + + <row> + <entry><structfield>objsubid</structfield></entry> + <entry><type>int4</type></entry> + <entry></entry> + <entry> + For a security label on a table column, this is the column number (the + <structfield>objoid</> and <structfield>classoid</> refer to + the table itself). For all other object types, this column is + zero. + </entry> + </row> + + <row> + <entry><structfield>provider</structfield></entry> + <entry><type>text</type></entry> + <entry></entry> + <entry>The label provider associated with this label.</entry> + </row> + + <row> + <entry><structfield>label</structfield></entry> + <entry><type>text</type></entry> + <entry></entry> + <entry>The security label applied to this object.</entry> + </row> + </tbody> + </tgroup> + </table> + </sect1> + <sect1 id="catalog-pg-shdepend"> <title><structname>pg_shdepend</structname></title> @@ -5884,6 +5960,11 @@ </row> <row> + <entry><link linkend="view-pg-seclabels"><structname>pg_seclabels</structname></link></entry> + <entry>security labels</entry> + </row> + + <row> <entry><link linkend="view-pg-settings"><structname>pg_settings</structname></link></entry> <entry>parameter settings</entry> </row> @@ -6791,6 +6872,97 @@ </sect1> + <sect1 id="view-pg-seclabels"> + <title><structname>pg_seclabels</structname></title> + + <indexterm zone="view-pg-seclabels"> + <primary>pg_seclabels</primary> + </indexterm> + + <para> + The view <structname>pg_seclabels</structname> provides information about + security labels. It as an easier-to-query version of the + <link linkend="catalog-pg-seclabel"><structname>pg_seclabel</></> catalog. + </para> + + <table> + <title><structname>pg_seclabels</> Columns</title> + + <tgroup cols="4"> + <thead> + <row> + <entry>Name</entry> + <entry>Type</entry> + <entry>References</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry><structfield>objoid</structfield></entry> + <entry><type>oid</type></entry> + <entry>any OID column</entry> + <entry>The OID of the object this security label pertains to</entry> + </row> + <row> + <entry><structfield>classoid</structfield></entry> + <entry><type>oid</type></entry> + <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry> + <entry>The OID of the system catalog this object appears in</entry> + </row> + <row> + <entry><structfield>objsubid</structfield></entry> + <entry><type>int4</type></entry> + <entry></entry> + <entry> + For a security label on a table column, this is the column number (the + <structfield>objoid</> and <structfield>classoid</> refer to + the table itself). For all other object types, this column is + zero. + </entry> + </row> + <row> + <entry><structfield>objtype</structfield></entry> + <entry><type>text</type></entry> + <entry></entry> + <entry> + The type of object to which this label applies, as text. + </entry> + </row> + <row> + <entry><structfield>objnamespace</structfield></entry> + <entry><type>oid</type></entry> + <entry><literal><link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.oid</literal></entry> + <entry> + The OID of the namespace for this object, if applicable; + otherwise NULL. + </entry> + </row> + <row> + <entry><structfield>objname</structfield></entry> + <entry><type>text</type></entry> + <entry></entry> + <entry> + The name of the object to which this label applies, as text. + </entry> + </row> + <row> + <entry><structfield>provider</structfield></entry> + <entry><type>text</type></entry> + <entry><literal><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link>.provider</literal></entry> + <entry>The label provider associated with this label.</entry> + </row> + <row> + <entry><structfield>label</structfield></entry> + <entry><type>text</type></entry> + <entry><literal><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link>.label</literal></entry> + <entry>The security label applied to this object.</entry> + </row> + </tbody> + </tgroup> + </table> + </sect1> + <sect1 id="view-pg-settings"> <title><structname>pg_settings</structname></title> diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index 7b97883d1bd..f5d67a20787 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. <!entity rollbackPrepared system "rollback_prepared.sgml"> <!entity rollbackTo system "rollback_to.sgml"> <!entity savepoint system "savepoint.sgml"> +<!entity securityLabel system "security_label.sgml"> <!entity select system "select.sgml"> <!entity selectInto system "select_into.sgml"> <!entity set system "set.sgml"> diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index 1b8402e78c1..8242b536d73 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -778,6 +778,16 @@ PostgreSQL documentation </para> </listitem> </varlistentry> + + <varlistentry> + <term><option>--security-label</option></term> + <listitem> + <para> + With this option, it also outputs security labels of database + objects to be dumped, if labeled. + </para> + </listitem> + </varlistentry> </variablelist> </para> </refsect1> diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml index 14fa1091128..68dcc35c50e 100644 --- a/doc/src/sgml/ref/pg_dumpall.sgml +++ b/doc/src/sgml/ref/pg_dumpall.sgml @@ -493,6 +493,15 @@ PostgreSQL documentation </para> </listitem> </varlistentry> + <varlistentry> + <term><option>--security-label</option></term> + <listitem> + <para> + With this option, it also outputs security labels of database + objects to be dumped, if labeled. + </para> + </listitem> + </varlistentry> </variablelist> </para> </refsect1> diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml index 9dc2511f5f3..78606969a98 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -329,6 +329,16 @@ </varlistentry> <varlistentry> + <term><option>--no-security-label</option></term> + <listitem> + <para> + Do not output commands to restore security labels, + even if the archive contains them. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><option>-P <replaceable class="parameter">function-name(argtype [, ...])</replaceable></option></term> <term><option>--function=<replaceable class="parameter">function-name(argtype [, ...])</replaceable></option></term> <listitem> diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml new file mode 100644 index 00000000000..7fce58bc13a --- /dev/null +++ b/doc/src/sgml/ref/security_label.sgml @@ -0,0 +1,194 @@ +<!-- +$PostgreSQL$ +PostgreSQL documentation +--> + +<refentry id="SQL-SECURITY-LABEL"> + <refmeta> + <refentrytitle>SECURITY LABEL</refentrytitle> + <manvolnum>7</manvolnum> + <refmiscinfo>SQL - Language Statements</refmiscinfo> + </refmeta> + + <refnamediv> + <refname>SECURITY LABEL</refname> + <refpurpose>define or change a security label applied to an object</refpurpose> + </refnamediv> + + <indexterm zone="sql-security-label"> + <primary>SECURITY LABEL</primary> + </indexterm> + + <refsynopsisdiv> +<synopsis> +SECURITY LABEL [ FOR <replaceable class="PARAMETER">provider</replaceable> ] ON +{ + TABLE <replaceable class="PARAMETER">object_name</replaceable> | + COLUMN <replaceable class="PARAMETER">table_name</replaceable>.<replaceable class="PARAMETER">column_name</replaceable> | + AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> (<replaceable class="PARAMETER">agg_type</replaceable> [, ...] ) | + DOMAIN <replaceable class="PARAMETER">object_name</replaceable> | + FUNCTION <replaceable class="PARAMETER">function_name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) | + LARGE OBJECT <replaceable class="PARAMETER">large_object_oid</replaceable> | + [ PROCEDURAL ] LANGUAGE <replaceable class="PARAMETER">object_name</replaceable> | + SCHEMA <replaceable class="PARAMETER">object_name</replaceable> | + SEQUENCE <replaceable class="PARAMETER">object_name</replaceable> | + TYPE <replaceable class="PARAMETER">object_name</replaceable> | + VIEW <replaceable class="PARAMETER">object_name</replaceable> +} IS '<replaceable class="PARAMETER">label</replaceable>' +</synopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para> + <command>SECURITY LABEL</command> 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 + <function>register_label_provider</>. + </para> + + <note> + <para> + <function>register_label_provider</> is not an SQL function; it can + only be called from C code loaded into the backend. + </para> + </note> + + <para> + 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. + <productname>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 + <productname>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. + </para> + </refsect1> + + <refsect1> + <title>Parameters</title> + + <variablelist> + <varlistentry> + <term><replaceable class="parameter">object_name</replaceable></term> + <term><replaceable class="parameter">table_name.column_name</replaceable></term> + <term><replaceable class="parameter">agg_name</replaceable></term> + <term><replaceable class="parameter">function_name</replaceable></term> + <listitem> + <para> + The name of the object to be commented. Names of tables, + aggregates, domains, functions, sequences, types, and views can + be schema-qualified. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">provider</replaceable></term> + <listitem> + <para> + 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. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">argmode</replaceable></term> + + <listitem> + <para> + The mode of a function argument: <literal>IN</>, <literal>OUT</>, + <literal>INOUT</>, or <literal>VARIADIC</>. + If omitted, the default is <literal>IN</>. + Note that <command>COMMENT ON FUNCTION</command> does not actually pay + any attention to <literal>OUT</> arguments, since only the input + arguments are needed to determine the function's identity. + So it is sufficient to list the <literal>IN</>, <literal>INOUT</>, + and <literal>VARIADIC</> arguments. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">argname</replaceable></term> + + <listitem> + <para> + The name of a function argument. + Note that <command>COMMENT ON FUNCTION</command> does not actually pay + any attention to argument names, since only the argument data + types are needed to determine the function's identity. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">argtype</replaceable></term> + + <listitem> + <para> + The data type(s) of the function's arguments (optionally + schema-qualified), if any. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">large_object_oid</replaceable></term> + <listitem> + <para> + The OID of the large object. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>PROCEDURAL</literal></term> + + <listitem> + <para> + This is a noise word. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">label</replaceable></term> + <listitem> + <para> + The new security label, written as a string literal; or <literal>NULL</> + to drop the security label. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Examples</title> + + <para> + The following example shows how the security label of a table might + be changed. + +<programlisting> +SECURITY LABEL FOR selinux ON TABLE mytable IS 'system_u:object_r:sepgsql_table_t:s0'; +</programlisting> + </para> + </refsect1> + + <refsect1> + <title>Compatibility</title> + <para> + There is no <command>SECURITY LABEL</command> command in the SQL standard. + </para> + </refsect1> +</refentry> diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index 052fe0e8fb2..463746cda3a 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 f4a7eb09dca..6a47f398ed8 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 62598ee8f89..18e07eb956a 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 651ffc61b96..09574c3e82c 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 4e9bf43ad5f..9d2a7322457 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 00000000000..417ad88d557 --- /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 deaeb761d4a..e07aa3ead23 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 6b6cd9966ce..8d083c8796d 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 40bd7a39325..4054cb1bc7b 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 <boolean> copy_from %type <ival> opt_column event cursor_options opt_hold opt_set_data -%type <objtype> reindex_type drop_type comment_type +%type <objtype> reindex_type drop_type comment_type security_label_type %type <node> 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 <str> OptTableSpace OptConsTableSpace OptTableSpaceOwner %type <list> opt_check_option +%type <str> opt_provider security_label + %type <target> xml_attribute_el %type <list> xml_attribute_list xml_attributes %type <node> 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 <provider>] ON <object> IS <label> + * + * As with COMMENT ON, <object> can refer to various types of database + * objects (e.g. TABLE, COLUMN, etc.). + * + *****************************************************************************/ + +SecLabelStmt: + SECURITY LABEL opt_provider ON security_label_type any_name + IS security_label + { + SecLabelStmt *n = makeNode(SecLabelStmt); + n->provider = $3; + n->objtype = $5; + n->objname = $6; + n->objargs = NIL; + n->label = $8; + $$ = (Node *) n; + } + | SECURITY LABEL opt_provider ON AGGREGATE func_name aggr_args + IS security_label + { + SecLabelStmt *n = makeNode(SecLabelStmt); + n->provider = $3; + n->objtype = OBJECT_AGGREGATE; + n->objname = $6; + n->objargs = $7; + n->label = $9; + $$ = (Node *) n; + } + | SECURITY LABEL opt_provider ON FUNCTION func_name func_args + IS security_label + { + SecLabelStmt *n = makeNode(SecLabelStmt); + n->provider = $3; + n->objtype = OBJECT_FUNCTION; + n->objname = $6; + n->objargs = extractArgTypes($7); + n->label = $9; + $$ = (Node *) n; + } + | SECURITY LABEL opt_provider ON LARGE_P OBJECT_P NumericOnly + IS security_label + { + SecLabelStmt *n = makeNode(SecLabelStmt); + n->provider = $3; + n->objtype = OBJECT_LARGEOBJECT; + n->objname = list_make1($7); + n->objargs = NIL; + n->label = $9; + $$ = (Node *) n; + } + | SECURITY LABEL opt_provider ON opt_procedural LANGUAGE any_name + IS security_label + { + SecLabelStmt *n = makeNode(SecLabelStmt); + n->provider = $3; + n->objtype = OBJECT_LANGUAGE; + n->objname = $7; + n->objargs = NIL; + n->label = $9; + $$ = (Node *) n; + } + ; + +opt_provider: FOR ColId_or_Sconst { $$ = $2; } + | /* empty */ { $$ = NULL; } + ; + +security_label_type: + COLUMN { $$ = OBJECT_COLUMN; } + | SCHEMA { $$ = OBJECT_SCHEMA; } + | SEQUENCE { $$ = OBJECT_SEQUENCE; } + | TABLE { $$ = OBJECT_TABLE; } + | DOMAIN_P { $$ = OBJECT_TYPE; } + | TYPE_P { $$ = OBJECT_TYPE; } + | VIEW { $$ = OBJECT_VIEW; } + ; + +security_label: Sconst { $$ = $1; } + | NULL_P { $$ = NULL; } + ; + /***************************************************************************** * * QUERY: @@ -11049,6 +11138,7 @@ unreserved_keyword: | INVOKER | ISOLATION | KEY + | LABEL | LANGUAGE | LARGE_P | LAST_P diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index f69559f0e43..75cb354ea89 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -37,6 +37,7 @@ #include "commands/prepare.h" #include "commands/proclang.h" #include "commands/schemacmds.h" +#include "commands/seclabel.h" #include "commands/sequence.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" @@ -218,6 +219,7 @@ check_xact_readonly(Node *parsetree) case T_AlterUserMappingStmt: case T_DropUserMappingStmt: case T_AlterTableSpaceOptionsStmt: + case T_SecLabelStmt: PreventCommandIfReadOnly(CreateCommandTag(parsetree)); break; default: @@ -663,6 +665,10 @@ standard_ProcessUtility(Node *parsetree, CommentObject((CommentStmt *) parsetree); break; + case T_SecLabelStmt: + ExecSecLabelStmt((SecLabelStmt *) parsetree); + break; + case T_CopyStmt: { uint64 processed; @@ -1592,6 +1598,10 @@ CreateCommandTag(Node *parsetree) tag = "COMMENT"; break; + case T_SecLabelStmt: + tag = "SECURITY LABEL"; + break; + case T_CopyStmt: tag = "COPY"; break; @@ -2318,6 +2328,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_SecLabelStmt: + lev = LOGSTMT_DDL; + break; + case T_CopyStmt: if (((CopyStmt *) parsetree)->is_from) lev = LOGSTMT_MOD; diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index 6f0277ea570..8fa9a57bacb 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -103,6 +103,7 @@ typedef struct _restoreOptions * restore */ int use_setsessauth;/* Use SET SESSION AUTHORIZATION commands * instead of OWNER TO */ + int skip_seclabel; /* Skip security label entries */ char *superuser; /* Username to use as superuser */ char *use_role; /* Issue SET ROLE to this */ int dataOnly; diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index a73afec73ab..d1a9c54b91f 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -2275,6 +2275,10 @@ _tocEntryRequired(TocEntry *te, RestoreOptions *ropt, bool include_acls) if ((!include_acls || ropt->aclsSkip) && _tocEntryIsACL(te)) return 0; + /* If it's security labels, maybe ignore it */ + if (ropt->skip_seclabel && strcmp(te->desc, "SECURITY LABEL") == 0) + return 0; + /* Ignore DATABASE entry unless we should create it */ if (!ropt->createDB && strcmp(te->desc, "DATABASE") == 0) return 0; @@ -2341,6 +2345,8 @@ _tocEntryRequired(TocEntry *te, RestoreOptions *ropt, bool include_acls) (strcmp(te->desc, "ACL") == 0 && strncmp(te->tag, "LARGE OBJECT ", 13) == 0) || (strcmp(te->desc, "COMMENT") == 0 && + strncmp(te->tag, "LARGE OBJECT ", 13) == 0) || + (strcmp(te->desc, "SECURITY LABEL") == 0 && strncmp(te->tag, "LARGE OBJECT ", 13) == 0)) res = res & REQ_DATA; else diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 2e1486f2216..ff7d97a064b 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -70,6 +70,14 @@ typedef struct int objsubid; /* subobject (table column #) */ } CommentItem; +typedef struct +{ + const char *provider; /* label provider of this security label */ + const char *label; /* security label for an object */ + Oid classoid; /* object class (catalog OID) */ + Oid objoid; /* object OID */ + int objsubid; /* subobject (table column #) */ +} SecLabelItem; /* global decls */ bool g_verbose; /* User wants verbose narration of our @@ -125,6 +133,7 @@ static int binary_upgrade = 0; static int disable_dollar_quoting = 0; static int dump_inserts = 0; static int column_inserts = 0; +static int no_security_label = 0; static void help(const char *progname); @@ -141,6 +150,12 @@ static void dumpComment(Archive *fout, const char *target, static int findComments(Archive *fout, Oid classoid, Oid objoid, CommentItem **items); static int collectComments(Archive *fout, CommentItem **items); +static void dumpSecLabel(Archive *fout, const char *target, + const char *namespace, const char *owner, + CatalogId catalogId, int subid, DumpId dumpId); +static int findSecLabels(Archive *fout, Oid classoid, Oid objoid, + SecLabelItem **items); +static int collectSecLabels(Archive *fout, SecLabelItem **items); static void dumpDumpableObject(Archive *fout, DumpableObject *dobj); static void dumpNamespace(Archive *fout, NamespaceInfo *nspinfo); static void dumpType(Archive *fout, TypeInfo *tyinfo); @@ -300,6 +315,7 @@ main(int argc, char **argv) {"quote-all-identifiers", no_argument, "e_all_identifiers, 1}, {"role", required_argument, NULL, 3}, {"use-set-session-authorization", no_argument, &use_setsessauth, 1}, + {"no-security-label", no_argument, &no_security_label, 1}, {NULL, 0, NULL, 0} }; @@ -448,6 +464,8 @@ main(int argc, char **argv) outputNoTablespaces = 1; else if (strcmp(optarg, "use-set-session-authorization") == 0) use_setsessauth = 1; + else if (strcmp(optarg, "no-security-label") == 0) + no_security_label = 1; else { fprintf(stderr, @@ -643,6 +661,12 @@ main(int argc, char **argv) do_sql_command(g_conn, "SET quote_all_identifiers = true"); /* + * Disables security label support if server version < v9.1.x + */ + if (!no_security_label && g_fout->remoteVersion < 90100) + no_security_label = 1; + + /* * Start serializable transaction to dump consistent data. */ do_sql_command(g_conn, "BEGIN"); @@ -839,6 +863,7 @@ help(const char *progname) printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --quote-all-identifiers quote all identifiers, even if not keywords\n")); printf(_(" --role=ROLENAME do SET ROLE before dump\n")); + printf(_(" --no-security-label do not dump security label assignments\n")); printf(_(" --use-set-session-authorization\n" " use SET SESSION AUTHORIZATION commands instead of\n" " ALTER OWNER commands to set ownership\n")); @@ -2058,6 +2083,11 @@ dumpBlob(Archive *AH, BlobInfo *binfo) NULL, binfo->rolname, binfo->dobj.catId, 0, binfo->dobj.dumpId); + /* Dump security label if any */ + dumpSecLabel(AH, cquery->data, + NULL, binfo->rolname, + binfo->dobj.catId, 0, binfo->dobj.dumpId); + /* Dump ACL if any */ if (binfo->blobacl) dumpACL(AH, binfo->dobj.catId, binfo->dobj.dumpId, "LARGE OBJECT", @@ -6569,12 +6599,15 @@ dumpNamespace(Archive *fout, NamespaceInfo *nspinfo) nspinfo->dobj.dependencies, nspinfo->dobj.nDeps, NULL, NULL); - /* Dump Schema Comments */ + /* Dump Schema Comments and Security Labels */ resetPQExpBuffer(q); appendPQExpBuffer(q, "SCHEMA %s", qnspname); dumpComment(fout, q->data, NULL, nspinfo->rolname, nspinfo->dobj.catId, 0, nspinfo->dobj.dumpId); + dumpSecLabel(fout, q->data, + NULL, nspinfo->rolname, + nspinfo->dobj.catId, 0, nspinfo->dobj.dumpId); dumpACL(fout, nspinfo->dobj.catId, nspinfo->dobj.dumpId, "SCHEMA", qnspname, NULL, nspinfo->dobj.name, NULL, @@ -6699,13 +6732,16 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo) tyinfo->dobj.dependencies, tyinfo->dobj.nDeps, NULL, NULL); - /* Dump Type Comments */ + /* Dump Type Comments and Security Labels */ resetPQExpBuffer(q); appendPQExpBuffer(q, "TYPE %s", fmtId(tyinfo->dobj.name)); dumpComment(fout, q->data, tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId); + dumpSecLabel(fout, q->data, + tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, + tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId); PQclear(res); destroyPQExpBuffer(q); @@ -7075,13 +7111,16 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo) tyinfo->dobj.dependencies, tyinfo->dobj.nDeps, NULL, NULL); - /* Dump Type Comments */ + /* Dump Type Comments and Security Labels */ resetPQExpBuffer(q); appendPQExpBuffer(q, "TYPE %s", fmtId(tyinfo->dobj.name)); dumpComment(fout, q->data, tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId); + dumpSecLabel(fout, q->data, + tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, + tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId); PQclear(res); destroyPQExpBuffer(q); @@ -7199,13 +7238,16 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo) tyinfo->dobj.dependencies, tyinfo->dobj.nDeps, NULL, NULL); - /* Dump Domain Comments */ + /* Dump Domain Comments and Security Labels */ resetPQExpBuffer(q); appendPQExpBuffer(q, "DOMAIN %s", fmtId(tyinfo->dobj.name)); dumpComment(fout, q->data, tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId); + dumpSecLabel(fout, q->data, + tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, + tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId); destroyPQExpBuffer(q); destroyPQExpBuffer(delq); @@ -7299,13 +7341,16 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo) NULL, NULL); - /* Dump Type Comments */ + /* Dump Type Comments and Security Labels */ resetPQExpBuffer(q); appendPQExpBuffer(q, "TYPE %s", fmtId(tyinfo->dobj.name)); dumpComment(fout, q->data, tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId); + dumpSecLabel(fout, q->data, + tyinfo->dobj.namespace->dobj.name, tyinfo->rolname, + tyinfo->dobj.catId, 0, tyinfo->dobj.dumpId); PQclear(res); destroyPQExpBuffer(q); @@ -7623,12 +7668,15 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang) plang->dobj.dependencies, plang->dobj.nDeps, NULL, NULL); - /* Dump Proc Lang Comments */ + /* Dump Proc Lang Comments and Security Labels */ resetPQExpBuffer(defqry); appendPQExpBuffer(defqry, "LANGUAGE %s", qlanname); dumpComment(fout, defqry->data, NULL, "", plang->dobj.catId, 0, plang->dobj.dumpId); + dumpSecLabel(fout, defqry->data, + NULL, "", + plang->dobj.catId, 0, plang->dobj.dumpId); if (plang->lanpltrusted) dumpACL(fout, plang->dobj.catId, plang->dobj.dumpId, "LANGUAGE", @@ -8184,12 +8232,15 @@ dumpFunc(Archive *fout, FuncInfo *finfo) finfo->dobj.dependencies, finfo->dobj.nDeps, NULL, NULL); - /* Dump Function Comments */ + /* Dump Function Comments and Security Labels */ resetPQExpBuffer(q); appendPQExpBuffer(q, "FUNCTION %s", funcsig); dumpComment(fout, q->data, finfo->dobj.namespace->dobj.name, finfo->rolname, finfo->dobj.catId, 0, finfo->dobj.dumpId); + dumpSecLabel(fout, q->data, + finfo->dobj.namespace->dobj.name, finfo->rolname, + finfo->dobj.catId, 0, finfo->dobj.dumpId); dumpACL(fout, finfo->dobj.catId, finfo->dobj.dumpId, "FUNCTION", funcsig, NULL, funcsig_tag, @@ -9687,6 +9738,9 @@ dumpAgg(Archive *fout, AggInfo *agginfo) dumpComment(fout, q->data, agginfo->aggfn.dobj.namespace->dobj.name, agginfo->aggfn.rolname, agginfo->aggfn.dobj.catId, 0, agginfo->aggfn.dobj.dumpId); + dumpSecLabel(fout, q->data, + agginfo->aggfn.dobj.namespace->dobj.name, agginfo->aggfn.rolname, + agginfo->aggfn.dobj.catId, 0, agginfo->aggfn.dobj.dumpId); /* * Since there is no GRANT ON AGGREGATE syntax, we have to make the ACL @@ -10435,6 +10489,300 @@ dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId, } /* + * dumpSecLabel + * + * This routine is used to dump any security labels associated with the + * object handed to this routine. The routine takes a constant character + * string for the target part of the security-label command, plus + * the namespace and owner of the object (for labeling the ArchiveEntry), + * plus catalog ID and subid which are the lookup key for pg_seclabel, + * plus the dump ID for the object (for setting a dependency). + * If a matching pg_seclabel entry is found, it is dumped. + * + * Note: although this routine takes a dumpId for dependency purposes, + * that purpose is just to mark the dependency in the emitted dump file + * for possible future use by pg_restore. We do NOT use it for determining + * ordering of the label in the dump file, because this routine is called + * after dependency sorting occurs. This routine should be called just after + * calling ArchiveEntry() for the specified object. + */ +static void +dumpSecLabel(Archive *fout, const char *target, + const char *namespace, const char *owner, + CatalogId catalogId, int subid, DumpId dumpId) +{ + SecLabelItem *labels; + int nlabels; + int i; + PQExpBuffer query; + + /* do nothing, if --no-security-label is supplied */ + if (no_security_label) + return; + + /* Comments are schema not data ... except blob comments are data */ + if (strncmp(target, "LARGE OBJECT ", 13) != 0) + { + if (dataOnly) + return; + } + else + { + if (schemaOnly) + return; + } + + /* Search for security labels associated with catalogId, using table */ + nlabels = findSecLabels(fout, catalogId.tableoid, catalogId.oid, &labels); + + query = createPQExpBuffer(); + + for (i = 0; i < nlabels; i++) + { + /* + * Ignore label entries for which the subid doesn't match. + */ + if (labels[i].objsubid != subid) + continue; + + appendPQExpBuffer(query, + "SECURITY LABEL FOR %s ON %s IS ", + fmtId(labels[i].provider), target); + appendStringLiteralAH(query, labels[i].label, fout); + appendPQExpBuffer(query, ";\n"); + } + + if (query->len > 0) + { + ArchiveEntry(fout, nilCatalogId, createDumpId(), + target, namespace, NULL, owner, + false, "SECURITY LABEL", SECTION_NONE, + query->data, "", NULL, + &(dumpId), 1, + NULL, NULL); + } + destroyPQExpBuffer(query); +} + +/* + * dumpTableSecLabel + * + * As above, but dump security label for both the specified table (or view) + * and its columns. + */ +static void +dumpTableSecLabel(Archive *fout, TableInfo *tbinfo, const char *reltypename) +{ + SecLabelItem *labels; + int nlabels; + int i; + PQExpBuffer query; + PQExpBuffer target; + + /* do nothing, if --no-security-label is supplied */ + if (no_security_label) + return; + + /* SecLabel are SCHEMA not data */ + if (dataOnly) + return; + + /* Search for comments associated with relation, using table */ + nlabels = findSecLabels(fout, + tbinfo->dobj.catId.tableoid, + tbinfo->dobj.catId.oid, + &labels); + + /* If comments exist, build SECURITY LABEL statements */ + if (nlabels <= 0) + return; + + query = createPQExpBuffer(); + target = createPQExpBuffer(); + + for (i = 0; i < nlabels; i++) + { + const char *colname; + const char *provider = labels[i].provider; + const char *label = labels[i].label; + int objsubid = labels[i].objsubid; + + resetPQExpBuffer(target); + if (objsubid == 0) + { + appendPQExpBuffer(target, "%s %s", reltypename, + fmtId(tbinfo->dobj.name)); + } + else + { + colname = getAttrName(objsubid, tbinfo); + appendPQExpBuffer(target, "COLUMN %s.%s", + fmtId(tbinfo->dobj.name), + fmtId(colname)); + } + appendPQExpBuffer(query, "SECURITY LABEL FOR %s ON %s IS ", + fmtId(provider), target->data); + appendStringLiteralAH(query, label, fout); + appendPQExpBuffer(query, ";\n"); + } + if (query->len > 0) + { + resetPQExpBuffer(target); + appendPQExpBuffer(target, "%s %s", reltypename, + fmtId(tbinfo->dobj.name)); + ArchiveEntry(fout, nilCatalogId, createDumpId(), + target->data, + tbinfo->dobj.namespace->dobj.name, + NULL, tbinfo->rolname, + false, "SECURITY LABEL", SECTION_NONE, + query->data, "", NULL, + &(tbinfo->dobj.dumpId), 1, + NULL, NULL); + } + destroyPQExpBuffer(query); + destroyPQExpBuffer(target); +} + +/* + * findSecLabels + * + * Find the security label(s), if any, associated with the given object. + * All the objsubid values associated with the given classoid/objoid are + * found with one search. + */ +static int +findSecLabels(Archive *fout, Oid classoid, Oid objoid, SecLabelItem **items) +{ + /* static storage for table of security labels */ + static SecLabelItem *labels = NULL; + static int nlabels = -1; + + SecLabelItem *middle = NULL; + SecLabelItem *low; + SecLabelItem *high; + int nmatch; + + /* Get security labels if we didn't already */ + if (nlabels < 0) + nlabels = collectSecLabels(fout, &labels); + + /* + * Do binary search to find some item matching the object. + */ + low = &labels[0]; + high = &labels[nlabels - 1]; + while (low <= high) + { + middle = low + (high - low) / 2; + + if (classoid < middle->classoid) + high = middle - 1; + else if (classoid > middle->classoid) + low = middle + 1; + else if (objoid < middle->objoid) + high = middle - 1; + else if (objoid > middle->objoid) + low = middle + 1; + else + break; /* found a match */ + } + + if (low > high) /* no matches */ + { + *items = NULL; + return 0; + } + + /* + * Now determine how many items match the object. The search loop + * invariant still holds: only items between low and high inclusive could + * match. + */ + nmatch = 1; + while (middle > low) + { + if (classoid != middle[-1].classoid || + objoid != middle[-1].objoid) + break; + middle--; + nmatch++; + } + + *items = middle; + + middle += nmatch; + while (middle <= high) + { + if (classoid != middle->classoid || + objoid != middle->objoid) + break; + middle++; + nmatch++; + } + + return nmatch; +} + +/* + * collectSecLabels + * + * Construct a table of all security labels available for database objects. + * It's much faster to pull them all at once. + * + * The table is sorted by classoid/objid/objsubid for speed in lookup. + */ +static int +collectSecLabels(Archive *fout, SecLabelItem **items) +{ + PGresult *res; + PQExpBuffer query; + int i_label; + int i_provider; + int i_classoid; + int i_objoid; + int i_objsubid; + int ntups; + int i; + SecLabelItem *labels; + + query = createPQExpBuffer(); + + appendPQExpBuffer(query, + "SELECT label, provider, classoid, objoid, objsubid " + "FROM pg_catalog.pg_seclabel " + "ORDER BY classoid, objoid, objsubid"); + + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + + /* Construct lookup table containing OIDs in numeric form */ + i_label = PQfnumber(res, "label"); + i_provider = PQfnumber(res, "provider"); + i_classoid = PQfnumber(res, "classoid"); + i_objoid = PQfnumber(res, "objoid"); + i_objsubid = PQfnumber(res, "objsubid"); + + ntups = PQntuples(res); + + labels = (SecLabelItem *) malloc(ntups * sizeof(SecLabelItem)); + + for (i = 0; i < ntups; i++) + { + labels[i].label = PQgetvalue(res, i, i_label); + labels[i].provider = PQgetvalue(res, i, i_provider); + labels[i].classoid = atooid(PQgetvalue(res, i, i_classoid)); + labels[i].objoid = atooid(PQgetvalue(res, i, i_objoid)); + labels[i].objsubid = atoi(PQgetvalue(res, i, i_objsubid)); + } + + /* Do NOT free the PGresult since we are keeping pointers into it */ + destroyPQExpBuffer(query); + + *items = labels; + return ntups; +} + +/* * dumpTable * write out to fout the declarations (not data) of a user-defined table */ @@ -10950,6 +11298,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) /* Dump Table Comments */ dumpTableComment(fout, tbinfo, reltypename); + /* Dump Table Security Labels */ + dumpTableSecLabel(fout, tbinfo, reltypename); + /* Dump comments on inlined table constraints */ for (j = 0; j < tbinfo->ncheck; j++) { @@ -11663,12 +12014,15 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) } } - /* Dump Sequence Comments */ + /* Dump Sequence Comments and Security Labels */ resetPQExpBuffer(query); appendPQExpBuffer(query, "SEQUENCE %s", fmtId(tbinfo->dobj.name)); dumpComment(fout, query->data, tbinfo->dobj.namespace->dobj.name, tbinfo->rolname, tbinfo->dobj.catId, 0, tbinfo->dobj.dumpId); + dumpSecLabel(fout, query->data, + tbinfo->dobj.namespace->dobj.name, tbinfo->rolname, + tbinfo->dobj.catId, 0, tbinfo->dobj.dumpId); } if (!schemaOnly) diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index 9294e079e95..bf91d726ce8 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -69,6 +69,7 @@ static int disable_triggers = 0; static int inserts = 0; static int no_tablespaces = 0; static int use_setsessauth = 0; +static int no_security_label = 0; static int server_version; static FILE *OPF; @@ -133,6 +134,7 @@ main(int argc, char *argv[]) {"quote-all-identifiers", no_argument, "e_all_identifiers, 1}, {"role", required_argument, NULL, 3}, {"use-set-session-authorization", no_argument, &use_setsessauth, 1}, + {"no-security-label", no_argument, &no_security_label, 1}, {NULL, 0, NULL, 0} }; @@ -286,6 +288,8 @@ main(int argc, char *argv[]) no_tablespaces = 1; else if (strcmp(optarg, "use-set-session-authorization") == 0) use_setsessauth = 1; + else if (strcmp(optarg, "no-security-label") == 0) + no_security_label = 1; else { fprintf(stderr, @@ -371,6 +375,8 @@ main(int argc, char *argv[]) appendPQExpBuffer(pgdumpopts, " --quote-all-identifiers"); if (use_setsessauth) appendPQExpBuffer(pgdumpopts, " --use-set-session-authorization"); + if (no_security_label) + appendPQExpBuffer(pgdumpopts, " --no-security-label"); /* * If there was a database specified on the command line, use that, @@ -567,6 +573,7 @@ help(void) printf(_(" --no-tablespaces do not dump tablespace assignments\n")); printf(_(" --quote-all-identifiers quote all identifiers, even if not keywords\n")); printf(_(" --role=ROLENAME do SET ROLE before dump\n")); + printf(_(" --no-security-label do not dump security label assignments\n")); printf(_(" --use-set-session-authorization\n" " use SET SESSION AUTHORIZATION commands instead of\n" " ALTER OWNER commands to set ownership\n")); diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index b9f0e86bfcb..1ddba727515 100644 --- a/src/bin/pg_dump/pg_restore.c +++ b/src/bin/pg_dump/pg_restore.c @@ -76,6 +76,7 @@ main(int argc, char **argv) static int no_data_for_failed_tables = 0; static int outputNoTablespaces = 0; static int use_setsessauth = 0; + static int skip_seclabel = 0; struct option cmdopts[] = { {"clean", 0, NULL, 'c'}, @@ -116,6 +117,7 @@ main(int argc, char **argv) {"no-tablespaces", no_argument, &outputNoTablespaces, 1}, {"role", required_argument, NULL, 2}, {"use-set-session-authorization", no_argument, &use_setsessauth, 1}, + {"no-security-label", no_argument, &skip_seclabel, 1}, {NULL, 0, NULL, 0} }; @@ -262,6 +264,8 @@ main(int argc, char **argv) outputNoTablespaces = 1; else if (strcmp(optarg, "use-set-session-authorization") == 0) use_setsessauth = 1; + else if (strcmp(optarg, "no-security-label") == 0) + skip_seclabel = 1; else { fprintf(stderr, @@ -337,6 +341,7 @@ main(int argc, char **argv) opts->noDataForFailedTables = no_data_for_failed_tables; opts->noTablespace = outputNoTablespaces; opts->use_setsessauth = use_setsessauth; + opts->skip_seclabel = skip_seclabel; if (opts->formatName) { @@ -442,6 +447,7 @@ usage(const char *progname) " do not restore data of tables that could not be\n" " created\n")); printf(_(" --no-tablespaces do not restore tablespace assignments\n")); + printf(_(" --no-security-label do not restore security labels\n")); printf(_(" --role=ROLENAME do SET ROLE before restore\n")); printf(_(" --use-set-session-authorization\n" " use SET SESSION AUTHORIZATION commands instead of\n" diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 04c81844da5..51010e16217 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -631,8 +631,9 @@ psql_completion(char *text, int start, int end) "DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH", "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE", "REASSIGN", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK", - "SAVEPOINT", "SELECT", "SET", "SHOW", "START", "TABLE", "TRUNCATE", "UNLISTEN", - "UPDATE", "VACUUM", "VALUES", "WITH", NULL + "SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START", + "TABLE", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", "VALUES", "WITH", + NULL }; static const char *const backslash_commands[] = { @@ -2193,6 +2194,40 @@ psql_completion(char *text, int start, int end) COMPLETE_WITH_QUERY(Query_for_list_of_databases); } +/* SECURITY LABEL */ + else if (pg_strcasecmp(prev_wd, "SECURITY") == 0) + COMPLETE_WITH_CONST("LABEL"); + else if (pg_strcasecmp(prev2_wd, "SECURITY") == 0 && + pg_strcasecmp(prev_wd, "LABEL") == 0) + { + static const char *const list_SECURITY_LABEL_preposition[] = + {"ON", "FOR"}; + COMPLETE_WITH_LIST(list_SECURITY_LABEL_preposition); + } + else if (pg_strcasecmp(prev4_wd, "SECURITY") == 0 && + pg_strcasecmp(prev3_wd, "LABEL") == 0 && + pg_strcasecmp(prev2_wd, "FOR") == 0) + COMPLETE_WITH_CONST("ON"); + else if ((pg_strcasecmp(prev3_wd, "SECURITY") == 0 && + pg_strcasecmp(prev2_wd, "LABEL") == 0 && + pg_strcasecmp(prev_wd, "ON") == 0) || + (pg_strcasecmp(prev5_wd, "SECURITY") == 0 && + pg_strcasecmp(prev4_wd, "LABEL") == 0 && + pg_strcasecmp(prev3_wd, "FOR") == 0 && + pg_strcasecmp(prev_wd, "ON") == 0)) + { + static const char *const list_SECURITY_LABEL[] = + {"LANGUAGE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW", "COLUMN", + "AGGREGATE", "FUNCTION", "DOMAIN", "LARGE OBJECT", + NULL}; + + COMPLETE_WITH_LIST(list_SECURITY_LABEL); + } + else if (pg_strcasecmp(prev5_wd, "SECURITY") == 0 && + pg_strcasecmp(prev4_wd, "LABEL") == 0 && + pg_strcasecmp(prev3_wd, "ON") == 0) + COMPLETE_WITH_CONST("IS"); + /* SELECT */ /* naah . . . */ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index f88730e2d24..74f1e2418fa 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201009021 +#define CATALOG_VERSION_NO 201009271 #endif diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 38c48b95633..9fa11c5da05 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -281,6 +281,9 @@ DECLARE_UNIQUE_INDEX(pg_default_acl_oid_index, 828, on pg_default_acl using btre DECLARE_UNIQUE_INDEX(pg_db_role_setting_databaseid_rol_index, 2965, on pg_db_role_setting using btree(setdatabase oid_ops, setrole oid_ops)); #define DbRoleSettingDatidRolidIndexId 2965 +DECLARE_UNIQUE_INDEX(pg_seclabel_object_index, 3038, on pg_seclabel using btree(objoid oid_ops, classoid oid_ops, objsubid int4_ops, provider text_ops)); +#define SecLabelObjectIndexId 3038 + /* last step of initialization script: build the indexes declared above */ BUILD_INDICES diff --git a/src/include/catalog/pg_seclabel.h b/src/include/catalog/pg_seclabel.h new file mode 100644 index 00000000000..1fd7451ad00 --- /dev/null +++ b/src/include/catalog/pg_seclabel.h @@ -0,0 +1,43 @@ +/* ------------------------------------------------------------------------- + * + * pg_seclabel.h + * definition of the system "security label" relation (pg_seclabel) + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * ------------------------------------------------------------------------- + */ +#ifndef PG_SECLABEL_H +#define PG_SECLABEL_H + +#include "catalog/genbki.h" + +/* ---------------- + * pg_seclabel definition. cpp turns this into + * typedef struct FormData_pg_seclabel + * ---------------- + */ +#define SecLabelRelationId 3037 + +CATALOG(pg_seclabel,3037) BKI_WITHOUT_OIDS +{ + Oid objoid; /* OID of the object itself */ + Oid classoid; /* OID of table containing the object */ + int4 objsubid; /* column number, or 0 if not used */ + text provider; /* name of label provider */ + text label; /* security label of the object */ +} FormData_pg_seclabel; + +/* ---------------- + * compiler constants for pg_seclabel + * ---------------- + */ +#define Natts_pg_seclabel 5 +#define Anum_pg_seclabel_objoid 1 +#define Anum_pg_seclabel_classoid 2 +#define Anum_pg_seclabel_objsubid 3 +#define Anum_pg_seclabel_provider 4 +#define Anum_pg_seclabel_label 5 + +#endif /* PG_SECLABEL_H */ diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h index 560d837d744..1e59cd21d24 100644 --- a/src/include/catalog/toasting.h +++ b/src/include/catalog/toasting.h @@ -45,6 +45,7 @@ DECLARE_TOAST(pg_constraint, 2832, 2833); DECLARE_TOAST(pg_description, 2834, 2835); DECLARE_TOAST(pg_proc, 2836, 2837); DECLARE_TOAST(pg_rewrite, 2838, 2839); +DECLARE_TOAST(pg_seclabel, 3039, 3040); DECLARE_TOAST(pg_statistic, 2840, 2841); DECLARE_TOAST(pg_trigger, 2336, 2337); diff --git a/src/include/commands/seclabel.h b/src/include/commands/seclabel.h new file mode 100644 index 00000000000..4c3854e60c3 --- /dev/null +++ b/src/include/commands/seclabel.h @@ -0,0 +1,35 @@ +/* + * seclabel.h + * + * Prototypes for functions in commands/seclabel.c + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + */ +#ifndef SECLABEL_H +#define SECLABEL_H + +#include "catalog/objectaddress.h" +#include "nodes/primnodes.h" +#include "nodes/parsenodes.h" + +/* + * Internal APIs + */ +extern char *GetSecurityLabel(const ObjectAddress *object, + const char *provider); +extern void SetSecurityLabel(const ObjectAddress *object, + const char *provider, const char *label); +extern void DeleteSecurityLabel(const ObjectAddress *object); + +/* + * Statement and ESP hook support + */ +extern void ExecSecLabelStmt(SecLabelStmt *stmt); + +typedef void (*check_object_relabel_type)(const ObjectAddress *object, + const char *seclabel); +extern void register_label_provider(const char *provider, + check_object_relabel_type hook); + +#endif /* SECLABEL_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 35def5eed03..0d33a2ed5ff 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -347,6 +347,7 @@ typedef enum NodeTag T_AlterUserMappingStmt, T_DropUserMappingStmt, T_AlterTableSpaceOptionsStmt, + T_SecLabelStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 68de95e49fd..b2f0fef5139 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1851,6 +1851,20 @@ typedef struct CommentStmt } CommentStmt; /* ---------------------- + * SECURITY LABEL Statement + * ---------------------- + */ +typedef struct SecLabelStmt +{ + NodeTag type; + ObjectType objtype; /* Object's type */ + List *objname; /* Qualified name of the object */ + List *objargs; /* Arguments if needed (eg, for functions) */ + char *provider; /* Label provider (or NULL) */ + char *label; /* New security label to be assigned */ +} SecLabelStmt; + +/* ---------------------- * Declare Cursor Statement * * Note: the "query" field of DeclareCursorStmt is only used in the raw grammar diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 01005d83c2c..d3ea04b7f4a 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -209,6 +209,7 @@ PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD) PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD) +PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD) PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD) PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD) PG_KEYWORD("last", LAST_P, UNRESERVED_KEYWORD) diff --git a/src/test/regress/GNUmakefile b/src/test/regress/GNUmakefile index 368347527c6..b2b5b16153c 100644 --- a/src/test/regress/GNUmakefile +++ b/src/test/regress/GNUmakefile @@ -107,9 +107,9 @@ installdirs-tests: installdirs $(MKDIR_P) $(patsubst $(srcdir)/%/,'$(DESTDIR)$(pkglibdir)/regress/%',$(sort $(dir $(regress_data_files)))) -# Get some extra C modules from contrib/spi... +# Get some extra C modules from contrib/spi and contrib/dummy_seclabel... -all: refint$(DLSUFFIX) autoinc$(DLSUFFIX) +all: refint$(DLSUFFIX) autoinc$(DLSUFFIX) dummy_seclabel$(DLSUFFIX) refint$(DLSUFFIX): $(top_builddir)/contrib/spi/refint$(DLSUFFIX) cp $< $@ @@ -117,12 +117,17 @@ refint$(DLSUFFIX): $(top_builddir)/contrib/spi/refint$(DLSUFFIX) autoinc$(DLSUFFIX): $(top_builddir)/contrib/spi/autoinc$(DLSUFFIX) cp $< $@ +dummy_seclabel$(DLSUFFIX): $(top_builddir)/contrib/dummy_seclabel/dummy_seclabel$(DLSUFFIX) + cp $< $@ + $(top_builddir)/contrib/spi/refint$(DLSUFFIX): $(top_srcdir)/contrib/spi/refint.c $(MAKE) -C $(top_builddir)/contrib/spi refint$(DLSUFFIX) $(top_builddir)/contrib/spi/autoinc$(DLSUFFIX): $(top_srcdir)/contrib/spi/autoinc.c $(MAKE) -C $(top_builddir)/contrib/spi autoinc$(DLSUFFIX) +$(top_builddir)/contrib/dummy_seclabel/dummy_seclabel$(DLSUFFIX): $(top_builddir)/contrib/dummy_seclabel/dummy_seclabel.c + $(MAKE) -C $(top_builddir)/contrib/dummy_seclabel dummy_seclabel$(DLSUFFIX) # Tablespace setup @@ -171,7 +176,8 @@ bigcheck: all clean distclean maintainer-clean: clean-lib # things built by `all' target - rm -f $(OBJS) refint$(DLSUFFIX) autoinc$(DLSUFFIX) pg_regress_main.o pg_regress.o pg_regress$(X) + rm -f $(OBJS) refint$(DLSUFFIX) autoinc$(DLSUFFIX) dummy_seclabel$(DLSUFFIX) + rm -f pg_regress_main.o pg_regress.o pg_regress$(X) # things created by various check targets rm -f $(output_files) $(input_files) rm -rf testtablespace diff --git a/src/test/regress/expected/.gitignore b/src/test/regress/expected/.gitignore index e14ce3aa673..93c56c85a09 100644 --- a/src/test/regress/expected/.gitignore +++ b/src/test/regress/expected/.gitignore @@ -5,4 +5,5 @@ /largeobject.out /largeobject_1.out /misc.out +/security_label.out /tablespace.out diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index ee3bd3b0339..c7a796a2a54 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1276,8 +1276,8 @@ drop table cchild; -- Check that ruleutils are working -- SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schema' ORDER BY viewname; - viewname | definition ------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + viewname | definition +-----------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- iexit | SELECT ih.name, ih.thepath, interpt_pp(ih.thepath, r.thepath) AS exit FROM ihighway ih, ramp r WHERE (ih.thepath ## r.thepath); pg_cursors | SELECT c.name, c.statement, c.is_holdable, c.is_binary, c.is_scrollable, c.creation_time FROM pg_cursor() c(name, statement, is_holdable, is_binary, is_scrollable, creation_time); pg_group | SELECT pg_authid.rolname AS groname, pg_authid.oid AS grosysid, ARRAY(SELECT pg_auth_members.member FROM pg_auth_members WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist FROM pg_authid WHERE (NOT pg_authid.rolcanlogin); @@ -1287,6 +1287,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem pg_prepared_xacts | SELECT p.transaction, p.gid, p.prepared, u.rolname AS owner, d.datname AS database FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid) LEFT JOIN pg_authid u ON ((p.ownerid = u.oid))) LEFT JOIN pg_database d ON ((p.dbid = d.oid))); pg_roles | SELECT pg_authid.rolname, pg_authid.rolsuper, pg_authid.rolinherit, pg_authid.rolcreaterole, pg_authid.rolcreatedb, pg_authid.rolcatupdate, pg_authid.rolcanlogin, pg_authid.rolconnlimit, '********'::text AS rolpassword, pg_authid.rolvaliduntil, s.setconfig AS rolconfig, pg_authid.oid FROM (pg_authid LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid)))); pg_rules | SELECT n.nspname AS schemaname, c.relname AS tablename, r.rulename, pg_get_ruledef(r.oid) AS definition FROM ((pg_rewrite r JOIN pg_class c ON ((c.oid = r.ev_class))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (r.rulename <> '_RETURN'::name); + pg_seclabels | (((((SELECT l.objoid, l.classoid, l.objsubid, CASE WHEN (rel.relkind = 'r'::"char") THEN 'table'::text WHEN (rel.relkind = 'v'::"char") THEN 'view'::text WHEN (rel.relkind = 'S'::"char") THEN 'sequence'::text ELSE NULL::text END AS objtype, rel.relnamespace AS objnamespace, CASE WHEN pg_table_is_visible(rel.oid) THEN quote_ident((rel.relname)::text) ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((rel.relname)::text)) 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)::text) ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((rel.relname)::text)) END || '.'::text) || (att.attname)::text) 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 ELSE NULL::text END AS objtype, pro.pronamespace AS objnamespace, (((CASE WHEN pg_function_is_visible(pro.oid) THEN quote_ident((pro.proname)::text) ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((pro.proname)::text)) END || '('::text) || pg_get_function_arguments(pro.oid)) || ')'::text) 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'::"char") 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)::text) ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((typ.typname)::text)) 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_largeobject'::regclass)::oid) 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)::text) 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)::text) 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); pg_settings | SELECT a.name, a.setting, a.unit, a.category, a.short_desc, a.extra_desc, a.context, a.vartype, a.source, a.min_val, a.max_val, a.enumvals, a.boot_val, a.reset_val, a.sourcefile, a.sourceline FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline); pg_shadow | SELECT pg_authid.rolname AS usename, pg_authid.oid AS usesysid, pg_authid.rolcreatedb AS usecreatedb, pg_authid.rolsuper AS usesuper, pg_authid.rolcatupdate AS usecatupd, pg_authid.rolpassword AS passwd, (pg_authid.rolvaliduntil)::abstime AS valuntil, s.setconfig AS useconfig FROM (pg_authid LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid)))) WHERE pg_authid.rolcanlogin; pg_stat_activity | SELECT s.datid, d.datname, s.procpid, s.usesysid, u.rolname AS usename, s.application_name, s.client_addr, s.client_port, s.backend_start, s.xact_start, s.query_start, s.waiting, s.current_query FROM pg_database d, pg_stat_get_activity(NULL::integer) s(datid, procpid, usesysid, application_name, current_query, waiting, xact_start, query_start, backend_start, client_addr, client_port), pg_authid u WHERE ((s.datid = d.oid) AND (s.usesysid = u.oid)); @@ -1333,7 +1334,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem shoelace_obsolete | SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM shoelace WHERE (NOT (EXISTS (SELECT shoe.shoename FROM shoe WHERE (shoe.slcolor = shoelace.sl_color)))); street | SELECT r.name, r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ## r.thepath); toyemp | SELECT emp.name, emp.age, emp.location, (12 * emp.salary) AS annualsal FROM emp; -(55 rows) +(56 rows) SELECT tablename, rulename, definition FROM pg_rules ORDER BY tablename, rulename; diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 1d9e1100448..9596b0b712b 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -114,6 +114,7 @@ SELECT relname, relhasindex pg_pltemplate | t pg_proc | t pg_rewrite | t + pg_seclabel | t pg_shdepend | t pg_shdescription | t pg_statistic | t @@ -153,7 +154,7 @@ SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f -(142 rows) +(143 rows) -- -- another sanity check: every system catalog that has OIDs should have diff --git a/src/test/regress/input/security_label.source b/src/test/regress/input/security_label.source new file mode 100644 index 00000000000..810a721ca8f --- /dev/null +++ b/src/test/regress/input/security_label.source @@ -0,0 +1,84 @@ +-- +-- Test for facilities of security label +-- + +-- initial setups +SET client_min_messages TO 'warning'; + +DROP ROLE IF EXISTS seclabel_user1; +DROP ROLE IF EXISTS seclabel_user2; + +DROP TABLE IF EXISTS seclabel_tbl1; +DROP TABLE IF EXISTS seclabel_tbl2; +DROP TABLE IF EXISTS seclabel_tbl3; + +CREATE USER seclabel_user1; +CREATE USER seclabel_user2; + +CREATE TABLE seclabel_tbl1 (a int, b text); +CREATE TABLE seclabel_tbl2 (x int, y text); +CREATE VIEW seclabel_view1 AS SELECT * FROM seclabel_tbl2; +CREATE FUNCTION seclabel_four() RETURNS integer AS $$SELECT 4$$ language sql; +CREATE DOMAIN seclabel_domain AS text; + +ALTER TABLE seclabel_tbl1 OWNER TO seclabel_user1; +ALTER TABLE seclabel_tbl2 OWNER TO seclabel_user2; + +RESET client_min_messages; + +-- +-- Test of SECURITY LABEL statement without a plugin +-- +SECURITY LABEL ON TABLE seclabel_tbl1 IS 'classified'; -- fail +SECURITY LABEL FOR 'dummy' ON TABLE seclabel_tbl1 IS 'classified'; -- fail +SECURITY LABEL ON TABLE seclabel_tbl1 IS '...invalid label...'; -- fail +SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified'; -- fail + +-- Load dummy external security provider +LOAD '@libdir@/dummy_seclabel@DLSUFFIX@'; + +-- +-- Test of SECURITY LABEL statement with a plugin +-- +SET SESSION AUTHORIZATION seclabel_user1; + +SECURITY LABEL ON TABLE seclabel_tbl1 IS 'classified'; -- OK +SECURITY LABEL ON COLUMN seclabel_tbl1.a IS 'unclassified'; -- OK +SECURITY LABEL ON TABLE seclabel_tbl1 IS '...invalid label...'; -- fail +SECURITY LABEL FOR 'dummy' ON TABLE seclabel_tbl1 IS 'unclassified'; -- OK +SECURITY LABEL FOR 'unknown_seclabel' ON TABLE seclabel_tbl1 IS 'classified'; -- fail +SECURITY LABEL ON TABLE seclabel_tbl2 IS 'unclassified'; -- fail (not owner) +SECURITY LABEL ON TABLE seclabel_tbl1 IS 'secret'; -- fail (not superuser) +SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified'; -- fail (not found) + +SET SESSION AUTHORIZATION seclabel_user2; +SECURITY LABEL ON TABLE seclabel_tbl1 IS 'unclassified'; -- fail +SECURITY LABEL ON TABLE seclabel_tbl2 IS 'classified'; -- OK + +RESET SESSION AUTHORIZATION; + +SECURITY LABEL ON TABLE seclabel_tbl1 IS 'top secret'; -- OK +SECURITY LABEL ON VIEW seclabel_view1 IS 'classified'; -- OK +SECURITY LABEL ON FUNCTION seclabel_four() IS 'classified'; -- OK +SECURITY LABEL ON DOMAIN seclabel_domain IS 'classified'; -- OK +SECURITY LABEL ON LANGUAGE plpgsql IS 'unclassified'; -- OK +SECURITY LABEL ON SCHEMA public IS 'unclassified'; -- OK + +SELECT objtype, objname, provider, label FROM pg_seclabels + ORDER BY objtype, objname; + +SECURITY LABEL ON LANGUAGE plpgsql IS NULL; -- OK +SECURITY LABEL ON SCHEMA public IS NULL; -- OK + +-- clean up objects +DROP FUNCTION seclabel_four(); +DROP DOMAIN seclabel_domain; +DROP VIEW seclabel_view1; +DROP TABLE seclabel_tbl1; +DROP TABLE seclabel_tbl2; +DROP USER seclabel_user1; +DROP USER seclabel_user2; + +-- make sure we don't have any leftovers +SELECT objtype, objname, provider, label FROM pg_seclabels + ORDER BY objtype, objname; diff --git a/src/test/regress/output/security_label.source b/src/test/regress/output/security_label.source new file mode 100644 index 00000000000..da8f49d9dc9 --- /dev/null +++ b/src/test/regress/output/security_label.source @@ -0,0 +1,92 @@ +-- +-- Test for facilities of security label +-- +-- initial setups +SET client_min_messages TO 'warning'; +DROP ROLE IF EXISTS seclabel_user1; +DROP ROLE IF EXISTS seclabel_user2; +DROP TABLE IF EXISTS seclabel_tbl1; +DROP TABLE IF EXISTS seclabel_tbl2; +DROP TABLE IF EXISTS seclabel_tbl3; +CREATE USER seclabel_user1; +CREATE USER seclabel_user2; +CREATE TABLE seclabel_tbl1 (a int, b text); +CREATE TABLE seclabel_tbl2 (x int, y text); +CREATE VIEW seclabel_view1 AS SELECT * FROM seclabel_tbl2; +CREATE FUNCTION seclabel_four() RETURNS integer AS $$SELECT 4$$ language sql; +CREATE DOMAIN seclabel_domain AS text; +ALTER TABLE seclabel_tbl1 OWNER TO seclabel_user1; +ALTER TABLE seclabel_tbl2 OWNER TO seclabel_user2; +RESET client_min_messages; +-- +-- Test of SECURITY LABEL statement without a plugin +-- +SECURITY LABEL ON TABLE seclabel_tbl1 IS 'classified'; -- fail +ERROR: security label providers have been loaded +SECURITY LABEL FOR 'dummy' ON TABLE seclabel_tbl1 IS 'classified'; -- fail +ERROR: security label provider "dummy" is not loaded +SECURITY LABEL ON TABLE seclabel_tbl1 IS '...invalid label...'; -- fail +ERROR: security label providers have been loaded +SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified'; -- fail +ERROR: security label providers have been loaded +-- Load dummy external security provider +LOAD '@abs_builddir@/dummy_seclabel@DLSUFFIX@'; +-- +-- Test of SECURITY LABEL statement with a plugin +-- +SET SESSION AUTHORIZATION seclabel_user1; +SECURITY LABEL ON TABLE seclabel_tbl1 IS 'classified'; -- OK +SECURITY LABEL ON COLUMN seclabel_tbl1.a IS 'unclassified'; -- OK +SECURITY LABEL ON TABLE seclabel_tbl1 IS '...invalid label...'; -- fail +ERROR: '...invalid label...' is not a valid security label +SECURITY LABEL FOR 'dummy' ON TABLE seclabel_tbl1 IS 'unclassified'; -- OK +SECURITY LABEL FOR 'unknown_seclabel' ON TABLE seclabel_tbl1 IS 'classified'; -- fail +ERROR: security label provider "unknown_seclabel" is not loaded +SECURITY LABEL ON TABLE seclabel_tbl2 IS 'unclassified'; -- fail (not owner) +ERROR: must be owner of relation seclabel_tbl2 +SECURITY LABEL ON TABLE seclabel_tbl1 IS 'secret'; -- fail (not superuser) +ERROR: only superuser can set 'secret' label +SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified'; -- fail (not found) +ERROR: relation "seclabel_tbl3" does not exist +SET SESSION AUTHORIZATION seclabel_user2; +SECURITY LABEL ON TABLE seclabel_tbl1 IS 'unclassified'; -- fail +ERROR: must be owner of relation seclabel_tbl1 +SECURITY LABEL ON TABLE seclabel_tbl2 IS 'classified'; -- OK +RESET SESSION AUTHORIZATION; +SECURITY LABEL ON TABLE seclabel_tbl1 IS 'top secret'; -- OK +SECURITY LABEL ON VIEW seclabel_view1 IS 'classified'; -- OK +SECURITY LABEL ON FUNCTION seclabel_four() IS 'classified'; -- OK +SECURITY LABEL ON DOMAIN seclabel_domain IS 'classified'; -- OK +SECURITY LABEL ON LANGUAGE plpgsql IS 'unclassified'; -- OK +SECURITY LABEL ON SCHEMA public IS 'unclassified'; -- OK +SELECT objtype, objname, provider, label FROM pg_seclabels + ORDER BY objtype, objname; + objtype | objname | provider | label +----------+-----------------+----------+-------------- + column | seclabel_tbl1.a | dummy | unclassified + domain | seclabel_domain | dummy | classified + function | seclabel_four() | dummy | classified + language | plpgsql | dummy | unclassified + schema | public | dummy | unclassified + table | seclabel_tbl1 | dummy | top secret + table | seclabel_tbl2 | dummy | classified + view | seclabel_view1 | dummy | classified +(8 rows) + +SECURITY LABEL ON LANGUAGE plpgsql IS NULL; -- OK +SECURITY LABEL ON SCHEMA public IS NULL; -- OK +-- clean up objects +DROP FUNCTION seclabel_four(); +DROP DOMAIN seclabel_domain; +DROP VIEW seclabel_view1; +DROP TABLE seclabel_tbl1; +DROP TABLE seclabel_tbl2; +DROP USER seclabel_user1; +DROP USER seclabel_user2; +-- make sure we don't have any leftovers +SELECT objtype, objname, provider, label FROM pg_seclabels + ORDER BY objtype, objname; + objtype | objname | provider | label +---------+---------+----------+------- +(0 rows) + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index a05bfeb0348..3b99e867efe 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -76,7 +76,7 @@ ignore: random # ---------- test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete -test: privileges +test: privileges security_label test: misc # rules cannot run concurrently with any test that creates a view test: rules diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index e2f8351ad79..b348f0e1a94 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -88,6 +88,7 @@ test: delete test: namespace test: prepared_xacts test: privileges +test: security_label test: misc test: rules test: select_views diff --git a/src/test/regress/sql/.gitignore b/src/test/regress/sql/.gitignore index 0b7c2cf14dd..46c8112094c 100644 --- a/src/test/regress/sql/.gitignore +++ b/src/test/regress/sql/.gitignore @@ -4,4 +4,5 @@ /create_function_2.sql /largeobject.sql /misc.sql +/security_label.sql /tablespace.sql |