Add a SECURITY LABEL command.
authorRobert Haas
Tue, 28 Sep 2010 00:55:27 +0000 (20:55 -0400)
committerRobert Haas
Tue, 28 Sep 2010 00:55:27 +0000 (20:55 -0400)
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:
contrib/Makefile
contrib/dummy_seclabel/Makefile [new file with mode: 0644]
contrib/dummy_seclabel/dummy_seclabel.c [new file with mode: 0644]
doc/src/sgml/catalogs.sgml
doc/src/sgml/ref/allfiles.sgml
doc/src/sgml/ref/pg_dump.sgml
doc/src/sgml/ref/pg_dumpall.sgml
doc/src/sgml/ref/pg_restore.sgml
doc/src/sgml/ref/security_label.sgml [new file with mode: 0644]
doc/src/sgml/reference.sgml
src/backend/catalog/Makefile
src/backend/catalog/dependency.c
src/backend/catalog/system_views.sql
src/backend/commands/Makefile
src/backend/commands/seclabel.c [new file with mode: 0644]
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/parser/gram.y
src/backend/tcop/utility.c
src/bin/pg_dump/pg_backup.h
src/bin/pg_dump/pg_backup_archiver.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dumpall.c
src/bin/pg_dump/pg_restore.c
src/bin/psql/tab-complete.c
src/include/catalog/catversion.h
src/include/catalog/indexing.h
src/include/catalog/pg_seclabel.h [new file with mode: 0644]
src/include/catalog/toasting.h
src/include/commands/seclabel.h [new file with mode: 0644]
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/parser/kwlist.h
src/test/regress/GNUmakefile
src/test/regress/expected/.gitignore
src/test/regress/expected/rules.out
src/test/regress/expected/sanity_check.out
src/test/regress/input/security_label.source [new file with mode: 0644]
src/test/regress/output/security_label.source [new file with mode: 0644]
src/test/regress/parallel_schedule
src/test/regress/serial_schedule
src/test/regress/sql/.gitignore

index c1d3317c2d5a5a8af7f832f78a822aca8efc7cfd..b7773255341bdd19d10190884d6da22e407c5ee9 100644 (file)
@@ -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 (file)
index 0000000..105400f
--- /dev/null
@@ -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 (file)
index 0000000..8bd50a3
--- /dev/null
@@ -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);
+}
index ab11b1506530b2790ee61a653db14e7ea7ce7593..8e4081cb33c33744804b1f7afaa2b0c131a7620a 100644 (file)
       query rewrite rules
      
 
+     
+      pg_seclabel
+      security labels on database objects
+     
+
      
       pg_shdepend
       dependencies on shared objects
  
 
 
+  <structname>pg_seclabel</structname>
+
+  
+   pg_seclabel
+  
+
+  
+   The catalog pg_seclabel stores security
+   labels on database objects.  See the 
+    statement.
+  
+
+  
+   <structname>pg_seclabel</structname> Columns
+
+   
+    
+     
+      Name
+      Type
+      References
+      Description
+     
+    
+
+    
+     
+      objoid
+      oid
+      any OID column
+      The OID of the object this security label pertains to
+     
+
+     
+      classoid
+      oid
+      pg_class.oid
+      The OID of the system catalog this object appears in
+     
+
+     
+      objsubid
+      int4
+      
+      
+       For a security label on a table column, this is the column number (the
+       objoid and classoid refer to
+       the table itself).  For all other object types, this column is
+       zero.
+      
+     
+
+     
+      provider
+      text
+      
+      The label provider associated with this label.
+     
+
+     
+      label
+      text
+      
+      The security label applied to this object.
+     
+    
+   
+  
+
  
   <structname>pg_shdepend</structname>
 
       rules
      
 
+     
+      pg_seclabels
+      security labels
+     
+
      
       pg_settings
       parameter settings
 
  
 
+  <structname>pg_seclabels</structname>
+
+  
+   pg_seclabels
+  
+
+  
+   The view pg_seclabels provides information about
+   security labels.  It as an easier-to-query version of the
+   pg_seclabel catalog.
+  
+
+  
+   <structname>pg_seclabels</> Columns
+
+   
+    
+     
+      Name
+      Type
+      References
+      Description
+     
+    
+    
+     
+      objoid
+      oid
+      any OID column
+      The OID of the object this security label pertains to
+     
+     
+      classoid
+      oid
+      pg_class.oid
+      The OID of the system catalog this object appears in
+     
+     
+      objsubid
+      int4
+      
+      
+       For a security label on a table column, this is the column number (the
+       objoid and classoid refer to
+       the table itself).  For all other object types, this column is
+       zero.
+      
+     
+     
+      objtype
+      text
+      
+      
+         The type of object to which this label applies, as text.
+      
+     
+     
+      objnamespace
+      oid
+      pg_namespace.oid
+      
+       The OID of the namespace for this object, if applicable;
+       otherwise NULL.
+      
+     
+     
+      objname
+      text
+      
+      
+       The name of the object to which this label applies, as text.
+      
+     
+     
+      provider
+      text
+      pg_seclabel.provider
+      The label provider associated with this label.
+     
+     
+      label
+      text
+      pg_seclabel.label
+      The security label applied to this object.
+     
+    
+   
+  
+
  
   <structname>pg_settings</structname>
 
index 7b97883d1bd2789f466a5cdddc3c45f8a0052ccd..f5d67a207876cd40f64f4736575409e06cfa4b4b 100644 (file)
@@ -132,6 +132,7 @@ Complete list of usable sgml source files in this directory.
 
 
 
+
 
 
 
index 1b8402e78c11147c6cc26203164f9610c34d20bc..8242b536d7319f8c2fac4f1310724cc0e0d6d087 100644 (file)
@@ -778,6 +778,16 @@ PostgreSQL documentation
        
       
      
+
+     
+      
+      
+       
+        With this option, it also outputs security labels of database
+        objects to be dumped, if labeled.
+       
+      
+     
     
    
  
index 14fa1091128b80ab8a4ef937c0f3dd7ad99bc58f..68dcc35c50e59b8229089d84fa6ea52cbf5cfbb7 100644 (file)
@@ -493,6 +493,15 @@ PostgreSQL documentation
        
       
      
+     
+      
+      
+       
+        With this option, it also outputs security labels of database
+        objects to be dumped, if labeled.
+       
+      
+     
    
   
  
index 9dc2511f5f3da6186221cc2ed9b03748ce609d16..78606969a9864e2798e0a31ab7e69512f8345d87 100644 (file)
       
      
 
+     
+      
+      
+       
+        Do not output commands to restore security labels,
+        even if the archive contains them.
+       
+      
+     
+
      
       
       
diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml
new file mode 100644 (file)
index 0000000..7fce58b
--- /dev/null
@@ -0,0 +1,194 @@
+
+
+
+  SECURITY LABEL
+  7
+  SQL - Language Statements
+
+  SECURITY LABEL
+  define or change a security label applied to an object
+
+  SECURITY LABEL
+
+
+SECURITY LABEL [ FOR provider ] ON
+{
+  TABLE object_name |
+  COLUMN table_name.column_name |
+  AGGREGATE agg_name (agg_type [, ...] ) |
+  DOMAIN object_name |
+  FUNCTION function_name ( [ [ argmode ] [ argname ] argtype [, ...] ] ) |
+  LARGE OBJECT large_object_oid |
+  [ PROCEDURAL ] LANGUAGE object_name |
+  SCHEMA object_name |
+  SEQUENCE object_name |
+  TYPE object_name |
+  VIEW object_name
+} IS 'label'
+
+
+  Description
+
+  
+   SECURITY LABEL applies a security label to a database
+   object.  An arbitrary number of security labels, one per label provider, can
+   be associated with a given database object.  Label providers are loadable
+   modules which register themselves by using the function
+   register_label_provider.
+  
+
+  
+    
+      register_label_provider is not an SQL function; it can
+      only be called from C code loaded into the backend.
+    
+  
+
+  
+   The label provider determines whether a given a label is valid and whether
+   it is permissible to assign that label to a given object.  The meaning of a
+   given label is likewise at the discretion of the label provider.
+   PostgreSQL places no restrictions on whether or how a
+   label provider must interpret security labels; it merely provides a
+   mechanism for storing them.  In practice, this facility is intended to allow
+   integration with label-based mandatory access control (MAC) systems such as
+   SE-Linux.  Such systems make all access control decisions
+   based on object labels, rather than traditional discretionary access control
+   (DAC) concepts such as users and groups.
+  
+
+  Parameters
+
+  
+   
+    object_name
+    table_name.column_name
+    agg_name
+    function_name
+    
+     
+      The name of the object to be commented.  Names of tables,
+      aggregates, domains, functions, sequences, types, and views can
+      be schema-qualified.
+     
+    
+   
+
+   
+    provider
+    
+     
+      The name of the provider with which this label is to be associated.  The
+      named provider must be loaded and must consent to the proposed labeling
+      operation.  If exactly one provider is loaded, the provider name may be
+      omitted for brevity.
+     
+    
+   
+
+   
+    argmode
+
+    
+     
+      The mode of a function argument: IN, OUT,
+      INOUT, or VARIADIC.
+      If omitted, the default is IN.
+      Note that COMMENT ON FUNCTION does not actually pay
+      any attention to OUT arguments, since only the input
+      arguments are needed to determine the function's identity.
+      So it is sufficient to list the IN, INOUT,
+      and VARIADIC arguments.
+     
+    
+   
+
+   
+    argname
+
+    
+     
+      The name of a function argument.
+      Note that COMMENT ON FUNCTION does not actually pay
+      any attention to argument names, since only the argument data
+      types are needed to determine the function's identity.
+     
+    
+   
+
+   
+    argtype
+
+    
+     
+      The data type(s) of the function's arguments (optionally 
+      schema-qualified), if any.
+     
+    
+   
+
+   
+    large_object_oid
+    
+     
+      The OID of the large object.
+     
+    
+   
+
+    
+     PROCEDURAL
+
+     
+      
+       This is a noise word.
+      
+     
+    
+
+   
+    label
+    
+     
+      The new security label, written as a string literal; or NULL
+      to drop the security label.
+     
+    
+   
+  
+
+  Examples
+
+  
+   The following example shows how the security label of a table might
+   be changed.
+
+
+SECURITY LABEL FOR selinux ON TABLE mytable IS 'system_u:object_r:sepgsql_table_t:s0';
+
+  
+
+  Compatibility
+  
+   There is no SECURITY LABEL command in the SQL standard.
+  
+
index 052fe0e8fb21c7d46e2a3f2252500f95acc5778d..463746cda3a0553cb43c3ef76623aaa055fd7327 100644 (file)
    &rollbackPrepared;
    &rollbackTo;
    &savepoint;
+   &securityLabel;
    &select;
    &selectInto;
    &set;
index f4a7eb09dcaabacba34d7be0b19a7fb93bbf4889..6a47f398ed85abc3384961ace63550df354aa507 100644 (file)
@@ -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 \
     )
 
index 62598ee8f89c479fcf7bebd580c63f0597c0e770..18e07eb956a4f6eb4a4fcfd6bb8f0523996a984d 100644 (file)
@@ -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
index 651ffc61b9691123166dcd600220cdcedf4abd76..09574c3e82cf116294346cd8579d103990550d0f 100644 (file)
@@ -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; 
 
index 4e9bf43ad5f6e22be2d3eb2acb2acbf1a721e3d2..9d2a7322457dd4b3a67b37b3a03a40b8d18f5e0a 100644 (file)
@@ -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 (file)
index 0000000..417ad88
--- /dev/null
@@ -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);
+}
index deaeb761d4a90192489c7b8398000cc45f45c07c..e07aa3ead232dfe2ef8f543d98a058aeebada323 100644 (file)
@@ -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;
index 6b6cd9966ce29518d9f3ca3f008768a5816021c5..8d083c8796d9ce6bb3f629b0b186e082c07987c0 100644 (file)
@@ -1163,6 +1163,18 @@ _equalCommentStmt(CommentStmt *a, CommentStmt *b)
    return true;
 }
 
+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)
 {
@@ -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;
index 40bd7a393251cd2ed2b3c6f713ce1ce0cc47a599..4054cb1bc7bc0efa65f9bad4b9901911d1928112 100644 (file)
@@ -205,7 +205,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
        CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
        RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
        RuleActionStmt RuleActionStmtOrEmpty RuleStmt
-       SelectStmt TransactionStmt TruncateStmt
+       SecLabelStmt SelectStmt TransactionStmt TruncateStmt
        UnlistenStmt UpdateStmt VacuumStmt
        VariableResetStmt VariableSetStmt VariableShowStmt
        ViewStmt CheckPointStmt CreateConversionStmt
@@ -335,7 +335,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
 %type  copy_from
 
 %type    opt_column event cursor_options opt_hold opt_set_data
-%type     reindex_type drop_type comment_type
+%type     reindex_type drop_type comment_type security_label_type
 
 %type    fetch_args limit_clause select_limit_value
                offset_clause select_offset_value
@@ -423,6 +423,8 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
 %type         OptTableSpace OptConsTableSpace OptTableSpaceOwner
 %type    opt_check_option
 
+%type         opt_provider security_label
+
 %type  xml_attribute_el
 %type    xml_attribute_list xml_attributes
 %type    xml_root_version opt_xml_root_standalone
@@ -500,7 +502,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
 
    KEY
 
-   LANGUAGE LARGE_P LAST_P LC_COLLATE_P LC_CTYPE_P LEADING
+   LABEL LANGUAGE LARGE_P LAST_P LC_COLLATE_P LC_CTYPE_P LEADING
    LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP
    LOCATION LOCK_P LOGIN_P
 
@@ -739,6 +741,7 @@ stmt :
            | RevokeStmt
            | RevokeRoleStmt
            | RuleStmt
+           | SecLabelStmt
            | SelectStmt
            | TransactionStmt
            | TruncateStmt
@@ -4368,6 +4371,92 @@ comment_text:
            | NULL_P                            { $$ = NULL; }
        ;
 
+
+/*****************************************************************************
+ *
+ *  SECURITY LABEL [FOR ] ON  IS