Controlling SPD Server Resources with PROC SPDO and ACL Commands

Using PROC SPDO

Overview of PROC SPDO

PROC SPDO is the SAS procedure for the SPD Server operator interface.
PROC SPDO runs only on systems where the SAS is installed.

PROC SPDO Command Set

To invoke PROC SPDO, submit:
PROC SPDO LIB=libref ;
where libref is a LIBNAME that was previously allocated to the sasspds engine.
Currently there are two classes of PROC SPDO commands:
  • ACL commands
  • LIBNAME proxy commands
The ACL commands are described below with some simple examples that demonstrate their syntax and usage. More information about LIBNAME Proxy Commands is available in SPD Server Operator Interface Procedure (PROC SPDO) .

Using ACL

An SPD Server Access Control List ( ACL) permits three distinct levels of permission on a resource. First, you can grant UNIVERSAL permissions to SPD Server users who are not in the same ACL group as the resource owner. Second, you can grant GROUP permissions to SPD Server users who are in the same ACL group as the resource owner. Third, you can grant USER permissions to a specific SPD Server user ID. The precedence of permission checks is as follows:
  1. Check user-specific permissions first. If defined, the accessor gets these permissions.
  2. If a resource is owned by the same ACL group as the accessor, the accessor gets the resource's GROUP permissions.
  3. If the resource is owned by a different ACL group than the accessor, the accessor gets the resource's UNIVERSAL permissions.
To turn on LIBACLINHERIT permissions in your spdsserv.parm file, submit the statement:
LIBACLINHERIT ; . 
To turn off LIBACLINHERIT permissions in your spdsserv.parm file, submit the statement:
NOLIBACLINHERIT ; .
You can also use your libnames.parm file to turn on LIBACLINHERIT permissions.
To turn on LIBACLINHERIT permissions in your libnames.parm file, submit the statement:
LIBACLINHERIT=YES.
To turn off LIBACLINHERIT permissions in your libnames.parm file, submit the statement::
LIBACLINHERIT=NO.
If the LIBACLINHERIT parameter file option is turned on, the ACL precedence of permission checks changes. Turning on LIBACLINHERIT creates a LIBNAME ACL on the specified LIBNAME domain. The LIBNAME ACL grants users rights to all resources within the LIBNAME domain. When a LIBNAME ACL is created for a specified LIBNAME domain, the ACL precedence of permission checks becomes:
  1. Check user-specific permissions first. If defined, the accessor gets these permissions.
  2. If a resource is owned by the same ACL group as the accessor, the accessor gets the resource's GROUP permissions.
  3. LIBNAME ACL permissions are used for domains where LIBACLINHERIT is turned on.
  4. If the resource is owned by a different ACL group than the accessor, the accessor gets the resource's UNIVERSAL permissions.

ACL Concepts

ACL Groups

ACL groups are somewhat analogous to UNIX groups. Each SPD Server user ID can belong to one or more ACL groups.
The SPD Server administrator can affiliate a given SPD Server user ID with up to five ACL groups. When you connect to an SPD Server using a LIBNAME assignment, you assert a specific ACL group using the ACLGRP= option.
The ACLGRP= value in your LIBNAME assignment must match one of the five groups that the administrator defined for you. If you do not assert ACLGRP= in your LIBNAME assignment, the SPD Server affiliates you with your default ACL group. (This is the first group in the list of five.)
When defining user-specific ACL permissions, you can use an ACL group wherever you can use an explicit SPD Server user ACL. Using an ACL group grants privileges to the ACL group instead of only to a specific SPD Server user.

Column Security

SPD Server allows you to control access to table contents at the column level through the use of ACLs. Column security ACLs can be applied to individual users at the user level, or applied to collections of users at the group level. SPD Server enforces precedence for user and group ACL permissions: first user ACL permissions are applied, then group ACL restrictions are applied. SPD Server user permissions override SPD Server group permissions.
When you use an ACL statement to create a protected column in a table, all individual users or groups are automatically denied access to the protected column until they are explicitly granted ACL permission to access it. When you issue an ACL statement to grant or deny the contents of a table column to a single user or user group, the protected column automatically becomes unavailable to all individual users and user groups, unless they are specifically given access to the protected column.
Examine a scenario where a testing department hires a new member, Joe. Joe has applied for classified security clearance, but his security clearance level will not be certified for several weeks. All members of the department use an SPD Server table TESTING that contains a column of classified information. Joe needs access to all of the TESTING table except the protected column, and the rest of his group needs access to the whole TESTING table.
First, you submit a user-level ACL statement to restrict the secure column in table TESTING from Joe. Joe is explicitly denied access, but since the column is now a protected entity, all other TESTING table users are also denied access to the column by default. Once a column is protected via ACL security, explicit permissions must be granted in order for any user (or groups of users) to be able to access the column content. Instead of issuing user-level column ACL permissions to the rest of the testing group individually, you issue a group-level ACL column permission to the user group TESTGROUP that explicitly grants access to the protected column.
SPD Server reads the user-level ACL permissions first, and gives Joe access to the table TESTGROUP, but restricts him from the secure column. Then SPD Server reads the group ACL permissions, and grants all of the TESTGROUP members access to the full table, including the secure column. Joe is a member of TESTGROUP, but the user-level ACL permissions maintain precedence over group-level ACL permissions. This results in all of TESTGROUP having full table access, except Joe. Joe's user-level ACL column security restriction prevents him from accessing the classified column.
Now consider another scenario, where John manages a group DEVGROUP whose members record their billable project hours and codes in an SPD Server table. In that table, manager John keeps billing rate information based on employee salaries in a protected column RATE. Only John should be able to see the entire table, and the rest of the DEVGROUP should be able to see the table minus the RATE column. In this case, you create column security by protecting the RATE column with a user-level ACL permission statement for John. The DEVGROUP members can have full table permissions at the group level, but will not see the protected column because John's user-level column security ACLs will override any group-level ACLs for the DEVGROUP table.

Generic ACL

You can use generic ACL names for a class of resources that have a common prefix. You can use the asterisk symbol "*" as a wildcard. This permits you to make a single ACL entry instead of making explicit entries for each resource. For example, if you have tables named SALESNE, SALESSE, SALESMW, SALESSW, SALESPW, and SALESNW, you could use the wildcard symbol to create the generic ACL name, SALES*, to cover them all. You then would define your ACL permissions on the SALES* generic ACL.
When using PROC SPDO, use the /GENERIC command option to identify a generic ACL.
Note: If you specify /GENERIC when defining a table column ACL, the /GENERIC applies to the table name, not to the column name. You cannot use wildcards with column names.

LIBNAME ACL

You can control access permissions to an entire LIBNAME domain with the SPD Server ACL facility. When using PROC SPDO, use the /LIBNAME option to identify the LIBNAME domain ACL.

Persistent ACL

A persistent ACL entry is an ACL that is not removed from the ACL tables when the resource itself is deleted. When using PROC SPDO, use the /PERSIST command option to identify a persistent ACL.

Resource

A PROC SPDO resource is
  • a table (data set)
  • a table column (data set variable)
  • a catalog
  • a catalog entry
  • a utility file (for example, a VIEW, an MDDB, and so on)
  • a LIBNAME domain.

Two-Part Resource Name

Two-part names identify a column entry within a table. Use the normal SAS convention of table.column when specifying the table and column that you want to secure.
When issuing SPDO commands, you can use two-part names in any context that defines, modifies, lists, or deletes table-related ACLs. You can also specify the reserved word _ALL_ as the column name when using SPDO commands that support the _ALL_ resource name.

Giving Control to Others

You permit other SPD Server users to alter your own ACL entry by granting a specific user/group ACL entry. See MODIFY ACL and MODIFY ACL _ALL_ for more information about user-specific ACL entries.

Overview of the ACL Command Set

This section describes PROC SPDO commands that you use to create and maintain ACLs on SPD Server resources.
To perform an ACL-related command, you must first assert an ACL user ID to define the scope of your access. In addition, you might want to set up a scoping member type to access ACLs for resource types other than DATA. Then you can ADD, MODIFY, LIST, or DELETE ACLs within the scope that you set up. You can switch the scope of a user and/or member type at any point in a command sequence, and then continue with additional ACL commands in the new scope.

SET ACLTYPE memtype;

Sets the member type for subsequent ACL operations. Valid values are DATA, CATALOG, VIEW, and MDDB. The default is DATA.

SET ACLUSER [name];

Sets the SPD Server user scope for subsequent ACL operations. The user scope restricts your view to only those ACL records that have the specified user name as the owner of the ACL entry. If name is omitted, the default is the user who assigns the libref.
To actually perform an ACL operation on a resource entry, you must
  • be the ACL entry owner, or
  • have CONTROL access over the ACL entry, or
  • have ACLSPECIAL=YES enabled on your PROC SPDO LIBNAME connection.
Note: You must first issue a SET ACLUSER command before issuing any of the following ACL commands.

ADD ACL acl1 acl2... [C=cat T=type] [/options]

Creates new ACL entries acl1 acl2... where ACL entries acl1 acl2 ... can be one-part resource names or two-part table column names.
Add ACL Options
READ
Grants universal READ access to the resource.
WRITE
Grants universal WRITE access to the resource.
ALTER
Grants universal ALTER access to the resource.
GROUPREAD
Grants group READ access to the resource.
GROUPWRITE
Grants group WRITE access to the resource.
GROUPALTER
Grants group ALTER access to the resource.
GENERIC
Specifies that acl1 acl2... are generic ACLs.
PERSIST
Specifies that acl1 acl2... are persistent ACLs.
LIBNAME
Identifies the special LIBNAME domain resource.
MODEL=acl-name
Specifies the name of another ACL. This option requests the software to copy all the access permissions and access list entries from this ACL.
C=cat
Identifies the specified ACL names acl1 acl2... as the names of catalog entries in the catalog cat. You pair this value with the T= option.
T=type
Identifies the catalog entry type to associate with the specified ACLs acl1 acl2... when you specify the C=cat option.

ADD ACL Examples

Add LIBNAME Domain ACL

This ACL grants universal READ and group WRITE access.
     add acl/LIBNAME
        read
        groupwrite;

Add Resource ACL

This ACL for the resource MINE_JAN1999 grants universal READ and WRITE access.
add acl mine_jan1999/read write;

Add Generic ACL

This generic ACL for MINE* grants universal READ access.
add acl mine/generic read;

Add Column ACL

This ACL for the column MINE_JAN2006.SALARY grants group READ access and denies access to all others.
add acl mine_jan2006.salary/groupread;

Add Generic Column ACL

This ACL for the column MINE*.SALARY grants group READ access and denies access to all others.
     add acl mine.salary/generic
        groupread;

Add Catalog ACL

This ACL for the MYCAT catalog grants universal READ and group READ/WRITE access.
     set acltype catalog;
     add acl mycat/read
      groupread
      groupwrite;
 

Add Generic ACL for Catalog Entries

This ACL for catalog entries, MYCAT.MY*.CATAMS, grants universal READ and group READ access.
    set acltype catalog;
     add acl my
      c=mycat
      t=catams/generic
      read
      groupread;

MODIFY ACL and MODIFY ACL _ALL_

MODIFY ACL acl1 acl2... [C=cat T=type] /options user list;

MODIFY ACL _ALL_ /options user list;

Modifies existing ACLs for resources acl1 acl2... where ACL entries acl1 acl2... can be one-part resource names or two-part table column names. Specifying _ALL_ modifies all existing ACLs for which you have control access. Specifying _ALL_ as the table identifier in a two-part name modifies all tables for which the given column is matched. Specifying _ALL_ as the column identifier in a two-part name modifies all columns for which the given table is matched. The characteristics modified are specified by options and/or user list.
Modify ACL Options
READ
Grants universal READ access.
NOREAD
Removes universal READ access.
WRITE
Grants universal WRITE access.
NOWRITE
Removes universal WRITE access.
ALTER
Grants universal ALTER access.
NOALTER
Removes universal ALTER access.
GROUPREAD
Grants group READ access.
NOGROUPREAD
Removes group READ access.
GROUPWRITE
Grants group WRITE access.
NOGROUPWRITE
Removes group WRITE access.
GROUPALTER
Grants group ALTER access.
NOGROUPALTER
Removes group ALTER access.
GENERIC
Specifies that acl1 acl2... are generic ACLs.
LIBNAME
Identifies the special LIBNAME domain ACL.
C=cat
Identifies the selected ACLs as names of catalog entries from the catalog cat. This value must be paired with the T= option.
T=type
Identifies the catalog entry type used to qualify the selected ACLs when the C=cat option is specified.
userlist
can be user name = (Y/N,Y/N,Y/N,Y/N) for (READ,WRITE,ALTER,CONTROL).

MODIFY ACL Examples

Modify LIBNAME Domain ACL

This modifies a LIBNAME domain to set READ and WRITE access for a given user.
modify acl/LIBNAME
  ralph=(y,y,n,n);

Modify ACL MINE

This modifies ACL MINE_JAN2003 to deny universal WRITE access and add user-specific permissions.
     modify acl mine_jan2003/nowrite
       bolick=(y,n,n,n)
       johndoe=(n,n,n,n);

Modify Generic ACL

This modifies a generic ACL MINE* to add user-specific permissions.
modify acl mine/generic
  tom=(y,y,y,n); 

Modify All ACLs

This modifies all ACLs to grant READ access to a given user.
modify acl _all_/gene=(y,,,);

Modify Column ACL

This modifies column ACL, MINE_JAN2006.SALARY, to add explicit READ and WRITE access for a given user.
modify acl mine_jan2006.salary/ralph=(y,y,n,n);

Modify Generic Column ACL

This modifies generic column ACL, MINE*.SALARY, to add explicit READ and WRITE access for a given user.
modify acl mine.salary/generic
 debby=(y,y,n,n);

Modify ACL for a Catalog

This modifies catalog MYCAT to remove universal READ and group WRITE access.
set acltype catalog;
  modify acl mycat/noread nogroupwrite;

Modify Generic ACL for Catalog Entries

This modifies a generic ACL for catalog entries, MYCAT.MY*.CATAMS, to remove universal READ access.
     set acltype catalog;
      modify acl my
       c=mycat
       t=catams/generic noread;

LIST ACL and LIST ACL _ALL_

LIST ACL acl1 acl2... [/options];

LIST ACL _ALL_ [/options];

Lists information about specific ACLs acl1 acl2... where ACL entries acl1 acl2... can be one-part resource names or two-part (table.column) names. Specifying _ALL_ lists all existing resource ACLs for which you have control access. Specifying _ALL_ as the table identifier in a two-part name lists all tables for which the given column is matched. Specifying _ALL_ as the column identifier in a two-part name lists all columns for which the given table is matched.
List ACL Options:
GENERIC
Specifies that acl1 acl2 are generic ACLs.
LIBNAME
Identifies the special LIBNAME domain ACL.
C=cat
Identifies the selected ACLs as names of catalog entries from the catalog cat. This value must be paired with the T= option.
T=type
Identifies the catalog entry type used to qualify the selected ACLs when the C=cat option is specified.
VERBOSE
Performs the requested table ACL listing, followed by the column ACLs for a specified table(s). This is equivalent to a LIST ACL table followed by a LIST ACL table._ALL_.

LIST ACL Examples

List All ACL Entries

This lists all ACL entries for the current ACL type setting.
list acl _all_;

List a Generic ACL

This lists a generic ACL entry for MINE*.
list acl mine/generic;

List All Column ACLS for a Table

This lists all column ACLs for table MINE_JAN2003.
list acl mine_jan2003._all_;

List All Column ACLs for All Tables

This lists all column ACLs for all tables.
list acl _all._all_;

List a Specific Column

This lists the column ACL for MINE_JAN2006.SALARY.
list acl
 mine_jan2006.salary;

List All ACL Data for a Table

This provides all ACL information for table MINE_JAN2006.
list acl
 mine_jan2006/verbose;

List All ACLs for Catalogs

This lists all ACLs for the ACL type 'catalog'.
set acltype catalog;
 list acl _all_;

List All ACLs for a Catalog

This lists all ACLs for catalog MYCAT.?.CATAMS.]
set acltype catalog;

  list acl _all_ c=mycat t=catams;

DELETE ACL and DELETE ACL _ALL_

DELETE ACL acl1 acl2... [C=cat T=type] /options

DELETE _ALL_[C=cat T=type] /options;

Deletes existing ACLs for resources acl1 acl2... where ACL entries acl1 acl2... can be one-part resource names or two-part table.column names. Specifying _ALL_ deletes all existing resource ACLs for which you have control access. Specifying _ALL_ as the table identifier in a two-part name deletes all tables for which the given column is matched. Specifying _ALL_ as the column identifier in a two-part name deletes all columns for which the given table is matched.
Delete ACL Options:
GENERIC
Specifies that acl1 acl2 are generic ACLs.
LIBNAME
Identifies the special *LIBNAM* ACL.
C=cat
Identifies the selected ACLs as names of catalog entries from the catalog cat. This value must be paired with the T= option.
T=type
Identifies the catalog entry type used to qualify the selected ACLs when the C=cat option is specified.

DELETE ACL Examples

Delete a LIBNAME ACL

This deletes a LIBNAME ACL.
delete acl/LIBNAME;

Delete All ACLs for Current ACL Type

This deletes all the ACLs for the current ACL type.
delete acl _all_;

Delete a Resource ACL

This deletes ACL MINE_JAN2003.
delete acl mine_jan2003;

Delete a Generic ACL

This deletes a generic ACL MINE*.
delete acl mine/generic;

Delete a Column ACL

This deletes a column ACL on MINE_JAN2003.SALARY.
delete acl mine_jan2003.salary;

Delete All Column ACLs on a Table

This deletes all column ACLs on table KBIKE.
delete acl kbike._all_;

Delete All Column ACLs on All Tables

This deletes all column ACLs on all tables.
delete acl _all_._all_;

Delete a Catalog ACL

This deletes an ACL on the catalog RBIKE.
set acltype catalog;
 delete acl rbike;

Delete a Generic ACL on Catalog Entries

This deletes a generic ACL on the catalog entries MYCAT.MY*.CATAMS.
set acltype catalog;
delete acl my
 c=mycat
 t=catams/generic;

ACL Security Examples

Overview of Security Examples

If LIBACLINHERIT=YES is added to a LIBNAME definition, the ACL precedence of permission checks changes. In this case, the LIBNAME ACL is used to first give READ or WRITE access to the domain, and then to inherit ACLs for resources that are owned by the domain owner. When a user accesses resources in an owned domain by using LIBACLINHERIT=YES, the following precedence of permissions checks on the ACL resource:
  • If user-specific permissions are defined on the object for the accessor, the accessor gets these permissions.
  • If group-specific permissions are defined on the object for the accessor's group, the accessor gets these permissions.
  • If LIBNAME ACL permissions are defined for the accessor, the accessor gets these permissions on the object.
  • If LIBNAME ACL permissions are defined for the accessor's group, the accessor gets these permissions on the object.
  • Otherwise, the accessor gets UNIVERSAL ACLs on the resource.
The following are examples using LIBACLINHERIT:
Below is a listing of the libnames.parm files that are used in the code examples, along with a listing of users and groups in the password database.

libnames.parm:
----------------------------------
LIBNAME=d1
  pathname=/IDX1/spdsmgr/d1
  owner=admin ;
LIBNAME=d2
  pathname=/IDX1/spdsmgr/d2
  owner=prod1 ;
LIBNAME=colsec
  pathname=/IDX1/spdsmgr/colsec
  owner=boss ;
LIBNAME=onepath
  pathname=/IDX1/spdsmgr/onepath ;

Password database List:

User     Level  Entry Type  Group
-----------------------------------
ADMINGRP   0   GROUP ENTRY
GROUP1     0   GROUP ENTRY
GROUP2     0   GROUP ENTRY
GROUP3     0   GROUP ENTRY
GROUP4     0   GROUP ENTRY
PRODGRP    0   GROUP ENTRY
ADMIN1     7   user ID     ADMINGRP
ADMIN2     7   user ID     ADMINGRP
PROD1      7   user ID     PRODGRP
PROD2      7   user ID     PRODGRP
USER1      0   user ID     GROUP1
USER2      0   user ID     GROUP2
USER3      0   user ID     GROUP3
USER4      0   user ID     GROUP4
USER5      0   user ID     GROUP1
USER6      0   user ID     GROUP2
USER7      0   user ID     GROUP3
USER8      0   user ID     GROUP4
BOSS       7   user ID     ADMINGRP
EMPLOYEE   0   user ID

Domain Security

When the libname.parm option OWNER= is specified, no other user can access the domain unless the user is given permissions by the domain owner. Permissions to access a domain are given using a LIBNAME ACL statement.
The code example below uses a LIBNAME ACL statement to give access permissions to different groups.
LIBNAME d2 sasspds 'd2'
  server=zztop.5162
  user='prod1'
  password='spds123'
  IP=YES ;

/* Give permissions to LIBNAME */

PROC SPDO library=d2 ;

/* assign who owns the ACLs */

  set acluser prod1 ;

/* Give specific groups access */
/* to the domain. */

  add ACL / LIBNAME ;
  modify ACL /
  LIBNAME prodgrp=(y,y,y,y)
    group1=(y,y,n,n)
    group2=(y,n,n,n)
    group3=(y,n,n,n) ;

/* Give spedific users access to */
/* the domain */

  modify ACL /
  LIBNAME user7=(y,n,n,n)
    admin1=(y,n,n,n) ;
  list ACL _all_ ;
quit ;

The ID 'prod2' is in the group which has permissions to control the LIBNAME ACL. Any ID in that group can modify the LIBNAME ACL.
Because the ACL was created by user 'prod1', the user 'prod2' must use the user ID 'prod1' in order to modify the LIBNAME ACL. This is allowed because the group was given control. User 'prod1' still remains the owner of the LIBNAME ACL.
LIBNAME prod2d2 sasspds 'd2'
  server=zztop.5162
  user='prod1'
  password='spds123'
  IP=YES ;

PROC SPDO library=prod2d2 ;

/* Set user ID as 'user1', who owns */
/* the ACL to be modified */

  set acluser prod1 ;
  modify ACL /
  LIBNAME group1=(n,n,n,n)
    group4=(y,n,n,n) ;
  list ACL _all_ ;
quit ;
The second way that the LIBNAME ACL can be changed is by using a user ID that has ACL Special privileges. In the example below, the user 'admin1' uses the ACLSPECIAL= statement to modify the LIBNAME ACL. As in the previous example, the user 'admin1' must use the user ID of 'prod1'.
LIBNAME admin1d2 sasspds 'd2'
  server=zztop.5162
  user='admin1'
  password='spds123'
  ACLSPECIAL=YES
  IP=YES ;

PROC SPDO library=admin1d2 ;

/* The ACLSPECIAL= statement allows */
/* the user 'admin1' to operate under */
/* the user ID 'prod1', allowing the */
/* ACLs to be modified. */

  set acluser prod1 ;
  modify ACL /
  LIBNAME admingrp=(y,n,n,n) ;
    list ACL _all_ ;
quit ;

LIBACLINHERIT

If the LIBACLINHERIT parameter file option is turned on, the ACL precedence of permission checks changes. Turning on LIBACLINHERIT creates a LIBNAME ACL on the specified LIBNAME domain. The LIBNAME ACL grants users rights to all resources within the LIBNAME domain. When a LIBNAME ACL is created for a specified LIBNAME domain, the ACL precedence of permission checks becomes:
  1. Check user-specific permissions first. If defined, the accessor gets these permissions.
  2. If a resource is owned by the same ACL group as the accessor, the accessor gets the resource's GROUP permissions.
  3. LIBNAME ACL permissions are used for domains where LIBACLINHERIT is turned on.
  4. If the resource is owned by a different ACL group than the accessor, the accessor gets the resource's UNIVERSAL permissions.
The following is an example using LIBACLINHERIT:
/* information from libnames.parm                  */
/*                                                 */
/* LIBNAME=LIBINHER                                */
/*    pathname=/IDX1/spdsmgr/spds41test/libinher   */
/*    LIBACLINHERIT=YES                            */
/*    owner=admin;                                 */
/* LIBNAME=noinher                                 */
/*    pathname=/IDX1/spdsmgr/spds41test/noinher    */
/*    owner=admin;                                 */


LIBNAME libinher sasspds 'libinher'
   server=zztop.5129
   user='admin'
   password='spds123';

LIBNAME noinher sasspds 'noinher'
   server=zztop.5129
   user='admin'
   password='spds123';

data libinher.admins_table
     noinher.admins_table ;

   do i = 1 to 10;
     output;
   end;
run;


/* Set up LIBNAME access for user anonymous */

PROC SPDO library=libinher;


/* set who will own these ACLs */

set acluser admin;


/* Add a LIBNAME ACL to d1 */

add acl / LIBNAME;


/* Modify LIBNAME ACL Domain d1     */
/* Allow users in Group 1 read-only */
/* access to the domain             */
modify acl / LIBNAME read;

  list acl _all_;
quit;


/* Set up LIBNAME access for user anonymous */

PROC SPDO library=noinher;


/* Specify who owns these ACLs */

set acluser admin ;


/* add a LIBNAME ACL to d1 */

add acl / LIBNAME ;


/* Modify LIBNAME ACL Domain d1      */
/* Allow users in Group 1 read-only  */
/* access to the domain              */

modify acl / LIBNAME read ;

  list acl _all_;
quit;

LIBNAME a_inher sasspds 'libinher'
   server=zztop.5129
   user='anonymous';
LIBNAME a_noher sasspds 'noinher'
   server=zztop.5129
   user='anonymous';

PROC PRINT data=a_inher.admins_table;
   title 'with libaclinher';
run;

PROC PRINT data=a_noher.admins_table;
   title 'without libaclinher';
run;

Anonymous User Account

The SPD Server uses a general ID that is called 'anonymous'. Any person that can connect to the server can do so using the anonymous user ID. The anonymous ID cannot be removed from the password database using the psmgr utility and the delete command. If you want to prevent anonymous user ID access, the SPD Server administrator must use the psmgr utility to add a user called, "anonymou" to the password database, and keep the password secret.
Any table that is created by the anonymous user ID can be viewed by all users who have access to that table's domain. The anonymous ID does have the ability to place ACLs on the table to limit access.
/* John logs in using the anonymous */
/* user ID and creates a table      */

     LIBNAME john sasspds 'onepath'
       server=zztop.5162
       user='anonymous'
       password='anonymous'
       IP=YES ;

     data john.anonymous_table ;
       do i = 1 to 100 ;
       output ;
       end ;
     run ;

/* Mary can also log in as anonymous  */
/* and read the table that John       */
/* created.                           */

     LIBNAME mary sasspds 'onepath'
       server=zztop.5162
       user='anonymous'
       IP=YES ;

     PROC PRINT data=mary.anonymous_table
       (obs=10) ;
       title
         'mary reading anonymous_table' ;
     run ;

/* user1 can log in and read the table */
/* that John created                   */

     LIBNAME user1 sasspds 'onepath'
       server=zztop.5162
       user='user1'
       password='spds123'
       IP=YES ;

     PROC PRINT data=user1.anonymous_table
       (obs=10) ;
       title
         'user1 reading anonymous_table' ;
     run ;

/* Tables created by user ID anonymous */
/* can have ACLs                       */

     PROC SPDO library=john ;

/* assign who owns the ACL */

     set acluser anonymous ;

/* The MODIFY statement sets an ACl so */
/* only user ID 'anonymous' can read   */
/* the table                           */

     add ACL anonymous_table ;
     modify ACL anonymous_table /
       anonymous=(y,n,n,n);

     list ACL _all_;
     quit ;

/* Now, only user ID 'anonymous' can */
/* read the table                    */

     LIBNAME user1 sasspds 'onepath'
       server=zztop.5162
       user='user1'
       password='spds123'
       IP=YES ;

     PROC PRINT data=user1.anonymous_table
       (obs=10) ;
       title
         'user1 trying to read anonymous_table' ;
     run ;

     LIBNAME mary sasspds 'onepath'
       server=zztop.5162
       user='anonymous'
       password='anonymous'
       IP=YES ;

     PROC PRINT data=mary.anonymous_table
       (obs=10) ;
       title
         'mary reading anonymous_table' ;
     run ;

/* Mary can't write to anonymous_table */

     data mary.anonymous_table ;
       do i = 1 to 100 ;
       output ;
       end ;
     run ;

Read Only Tables

A common security measure in SPD Server assigns an SPD Server ID to act as the owner of a domain and to provide control over it.
Typically, one or two user IDs administer table loads and refreshes . These user IDs can perform all the jobs that are required to create, load, refresh, update, and administer SPD Server security. Using one or two user IDs centralizes the data administration on the server. More than one ID for data administration spreads responsibility and still provides backup. The following example demonstrates how to allow different groups access to the domain, tables, and how different groups can control resources in the domain.
      LIBNAME d1 sasspds 'd1'
        server=zztop.5162
        user='admin1'
        password='spds123'
        IP=YES ;

      PROC SPDO library=d1 ;

/* assign who owns the ACLs */

      set acluser admin1 ;

/* add a LIBNAME ACL to d1  */

      add ACL / LIBNAME ;
The MODIFY statement in the code below enables the following actions:
  • Any user in same group as admin can read, write, or alter tables and modify the LIBNAME access to the domain.
  • Users in group1 and group2 receive read access to the domain.
  • Users in group3 and group4 receive read and write access to the domain.
      modify ACL / LIBNAME
        admingrp=(y,y,y,y)
        group1=(y,n,n,n)
        group2=(y,n,n,n)
        group3=(y,y,n,n)
        group4=(y,y,n,n) ;

      list ACL _all_;
      quit ;

/* create two tables */

      data d1.admin1_table1 ;
        do i = 1 to 100 ;
        output ;
        end ;
      run ;

/* admin1 has write priviliges to */
/* the domain                     */

      data d1.admin1_table2 ;
        do i = 1 to 100 ;
        output ;
        end ;
      run ;


/* Generic ACLs allow all users to */
/* read tables created by admin1   */
/* unless a specific ACL is placed */
/* on a resource                   */

      PROC SPDO library=d1 ;

/* Assign who owna the ACLs */

      set acluser admin1 ;
The two ACL commands in the code below give read privileges to members of the ACL group 'ADMIN1' for any table that is created by admin1, who has read access to the domain.
This ACL is a good example for data marts and warehouses which DO NOT contain sensitive data. A GENERIC ACL gives broad access to tables in a domain. Generic ACLs must be used correctly (or not at all) if sensitive data needs to be restricted to specific users or groups of users.
If a table in a domain with generic ACLs is not specifically protected by its own ACL, there is a risk of allowing access by any user to sensitive data.
     add ACL / generic
       read ;
     modify ACL / generic read
       admingrp=(y,n,n,y) ;
     list ACL _all_;
     quit ;

/* Test access for a user in group1 */

    LIBNAME user1d1 sasspds 'd1'
      server=zztop.5162
      user='user1'
      password='spds123'
      IP=YES ;

    PROC PRINT data=user1d1.admin1_table1
      (obs=10) ;
      title
        'read admin1_table1 by user1' ;
    run ;

    PROC PRINT data=user1d1.admin1_table2
      (obs=10) ;
      title
        'read admin1_table2 by user1' ;
    run ;

/* Test access for a user in group2 */

    LIBNAME user2d1 sasspds 'd1'
      server=zztop.5162
      user='user2'
      password='spds123'
      IP=YES ;

    PROC PRINT data=user2d1.admin1_table1
      (obs=10) ;
      title
        'read admin1_table1 by user2' ;
    run ;

    PROC PRINT data=user2d1.admin1_table2
      (obs=10) ;
      title
        'read admin1_table2 by user2' ;
    run ;
When any ACL is placed on a specific table, that ACL takes precedence over the generic ACL. The ACL in the code below performs the following:
  • Gives read access of admin1_table2 to group1.
  • Gives the admingrp read and control of admin1_table2
  • Takes precedence over the generic read ACL, which prevents users that are not granted specific access to admin1_table2 from reading, writing, altering, or controling the table.
    PROC SPDO library=d1 ;

/* Assign who owns the ACLs */

    set acluser admin1 ;

/* This ACL takes precedence over the */
/* generic ACL for users that try to  */
/* access admin1_table2.              */

    add ACL admin1_table2 ;
    modify ACL admin1_table2 /
      group1=(y,n,n,n)
      admingrp=(y,n,n,y) ;
    list ACL _all_;
    quit ;

/* Test access for a user in group1 */

    LIBNAME user1d1 sasspds 'd1'
      server=zztop.5162
      user='user1'
      password='spds123'
      IP=YES ;

    PROC PRINT data=user1d1.admin1_table2
      (obs=10) ;
      title
        'read admin1_table2 by user1' ;
    run ;

/* Test access for a user in group2 */

    LIBNAME user2d1 sasspds 'd1'
      server=zztop.5162
      user='user2'
      password='spds123'
      IP=YES ;


    PROC PRINT data=user2d1.admin1_table2
      (obs=10) ;
      title
        'read admin1_table2 by user2' ;
    run ;

Domain Security and Group Access

This section of code provides an overview of SPD Server domain security and group access using PROC SPDO.
Permissions are often granted to a group of users rather than individual users. The example below shows how to provide the different groups of users access to the domain owned by the user ID "Admin", and then extends the access to the tables. This makes administration both simpler and more secure. Admin1 is the owner of the domain and can determine access to the resources. In the following example, PROC SPDO permits the following:
  • Any user ID in admingrp receives read/write/alter access to the domain
  • Any user ID in group1 or group2 receives read access to the domain
  • Any user ID in group3 or group4 receives read/write access to the domain
       LIBNAME d1 sasspds 'd1'
         server=zztop.5162
         user='admin'
         password='spds123
         IP=YES ;

       PROC SPDO library=d1 ;

 /* assign who owns the ACLs */

       set acluser admin ;

 /* add a LIBNAME ACL to d1 */

       add ACL / LIBNAME ;

/* Allow any user in same group */
/* as admin to read, write, or  */
/* alter tables in the domain   */

       modify ACL / LIBNAME
         admingrp=(y,y,y,n)
         group1=(y,n,n,n)
         group2=(y,n,n,n)
         group3=(y,y,n,n)
         group4=(y,y,n,n) ;

       list ACL _all_;

       run;

/* admin1 has write privileges to */
/* the domain                     */

       data d1.admin1_table1 ;
         do i = 1 to 100 ;
         output ;
         end ;
       run ;

/* Generic ACL allows all users to */
/* read tables created by admin1   */

       PROC SPDO library=d1 ;

/* assign who owns the ACLs */

       set acluser admin1 ;

/* Modify LIBNAME for groupread     */
/* and groupwrite.  The ACL MUST    */
/* inlcude groupread if other       */
/* users  in the same group as      */
/* admin2 need to be able to read   */
/* tables that were created by      */
/* admin2.                          */

       add ACL admin1_table1 /
         generic
         read
         groupread
         groupalter ;

       list ACL _all_;
       run;

/* admin1 has write privileges to    */
/* the domain                        */

       data d1.admin1_table2 ;
         do i = 1 to 100 ;
         output ;
         end ;
       run ;

/* generic ACL allows all users to    */
/* read the tables                    */

       PROC SPDO library=d1 ;

/* assign who owns the ACLs */

       set acluser admin1 ;

/* Add a table and modify LIBNAME ACL */
/* for groupread and groupwrite. The  */
/* ACL MUST include groupread to give */
/* users in the same group as admin2  */
/* the ability to read tables created */
/* by admin2                          */

      add ACL admin1_table2 /
        group1=(y,n,n,n)
        admingrp=(y,n,n,y) ;
        list ACL _all_;
      run;

/* admin2 has write privileges to the */
/* domain                             */

      data admin2d1.admin2_table ;
        do i = 1 to 100 ;
        output ;
        end ;
      run ;

/* Admin2 must use PROC SPDO to allow */
/* users read access to the table.    */
/* The PROC SPDO example below uses   */
/* generic syntax with a read.  This  */
/* provides any user outside of the   */
/* admingrp read access to tables     */
/* that were created by acdmin2.  The */
/* groupread and groupalter allow     */
/* access by users within admingrp.   */

     PROC SPDO library=admin2d1 ;

/* Assign who owns the ACLs */

     set acluser admin2 ;

/* Modify LIBNAME ACL for groupread   */
/* and groupwrite. The ACL MUST       */
/* include groupread if other users   */
/* in the same group as admin2 need   */
/* to read tables created by admin2.  */

     add ACL / generic
       read
       groupread
       groupalter ;

     list ACL _all_;

/* admin (same group) can read the     */
/* table                               */

     PROC PRINT data=d1.admin2_table
       (obs=10) ;
       title 'read by admin' ;
     run ;

/* Admin has been given the ability to */
/* modify or replace tables created by */
/* admin2 with 'groupalter'            */

     data d1.admin2_table ;
       do i = 1 to 100 ;
       output ;
       end ;
     run ;

/* Provide other users in same group   */
/* read access to the table            */

     PROC SPDO library=admin2d1 ;

/* assign who owns the ACLs */

     set acluser user3 ;

/* Modify LIBNAME ACL for groupread    */
/* and groupwrite. The ACL MUST        */
/* include groupread if other users in */
/* the same group as admin2 are to be  */
/* able to read tables that were       */
/* created by admin2                   */

     add ACL user3_table /
       groupread ;
     list ACL _all_;

Bringing a Table Offline to Refresh

When it is time to refresh the table, the first step is to revoke read privileges to all user IDs, except the ID that will perform the refresh.
      LIBNAME d2 sasspds 'd2'
        server=zztop.5162
        user='prod1'
        password='spds123'
        IP=YES ;
This example assumes that the Table prod1_table is already loaded in the domain and that the groups who use the table have access.
PROC SPDO library=d2 ;

/* assign who will owns these ACLs */

set acluser prod1 ;
Modify the table ACL in the following ways:
  • Revoke read and control by user IDs that are in the same group. This prevents locks during table refreshes.
  • Revoke read access by users that are in group1 through group4 to prevent locks during the refresh process.
    Note: If a user is actively accessing a data table when the ACLs for that table are modified, the user continues to have access. This situation can create a table lock that prevents the table refresh from occurring. By revoking the table's read privileges before the refresh occurs, new SPD Server jobs cannot access the table.
    Existing jobs will continue running and can finish under the lock. You can also use the special PROC SPDO operator commands to identify any users that might be running unattended jobs, and disconnect them so the refresh can take place.
          modify ACL prod1_table /
            prodgrp=(n,n,n,n)
            group1=(n,n,n,n)
            group2=(n,n,n,n)
            group3=(n,n,n,n)
            group4=(n,n,n,n) ;
    
Now, modify table ACLs to allow the user ID prod1 to perform table refreshes. Because user ID prod1 is part of prodgrp, that ID loses access to the table when the permissions are changed. Prod1, the domain and table owner, can still modify ACLs to gain access.
modify ACL prod1_table /
prod1=(y,y,y,y) ;
list ACL _all_;
quit;

Now user ID prod1 has full access to refresh the table.
data d2.prod1_table ;
do i = 1 to 100 ;
output ;
end ;
run ;

PROC SPDO library=d2 ;

/* Specify who owns the ACLs */

set acluser prod1 ;

There is no need to issue an add ACL command for prod1_table. Deleting a table or replacing a table does not delete the ACLs. The ACL for that table remains until:
  • The table ACL is deleted using PROC SPDO delete syntax.
  • The table is deleted and another user creates a table with the same name.
At that time, the ACLs have not been deleted. Deleting the table releases any rights that owner has on the table. The exception is when persistent ACLs are used.
After the table has been refreshed, the ACL can be modified to allow read access once again.
modify ACL prod1_table /
prodgrp=(y,n,n,y)
group1=(y,n,n,n)
group2=(y,n,n,n)
group3=(y,n,n,n)
group4=(y,n,n,n) ;
list ACL _all_ ;
run ;

Bringing a Domain Offline to Refresh Tables

When it is time to refresh the table(s), one approach to minimize contention and table locking is to revoke privileges of users and groups who will not be involved in the refreshing of tables in the domain.
This example assumes that the tables are already loaded in the domain and that the groups who use them have access.
  LIBNAME d2 sasspds 'd2'
    server=zztop.5162
    user='prod1'
    password='spds123'
    IP=YES ;

  PROC SPDO library=d2 ;

/* Assign who owns the ACLs */

   set acluser prod1 ;
It is possible to revoke read access at the LIBNAME or domain level, which allows the IDs that are used to refresh the warehouse complete control of resources in the domain. This example turns off all read access to the domain, except for IDs that are in the production group (prodgrp).
By doing this, the production IDs have full control over the tables and resources.
Note: Any user that is currently accessing the domain will continue to have access until they are disconnected. This can cause a lock to occur. The PROC SPDO special operator commands can be used to identify the user and disconnect the process so the refresh can take place.
modify ACL / LIBNAME
prodgrp=(y,y,y,y)
group1=(n,n,n,n)
group2=(n,n,n,n)
group3=(n,n,n,n)
group4=(n,n,n,n);
list ACL _all_ ;
run ;

/* Modify ACL for tables to be refreshed */

PROC SPDO library=d2 ;

/* set who owns the ACLs */

set acluser prod1 ;

/* Modify table ACL to revoke read and */
/* control by user IDs in same group, */
/* which prevents locks during table */
/* refreshes. */

modify ACL prod1_table /
prodgrp=(n,n,n,n);

/* Modify table ACL to allow the */
/* 'prod1' user ID to refresh the */
/* table. */

modify ACL prod1_table /
prod1=(y,y,y,y) ;
list ACL _all_;

/* refresh warehouse table(s) */

data d2.prod1_table ;
do i = 1 to 100 ;
output ;
end ;
run ;

PROC SPDO library=d2 ;

/* Assign who owns the ACLs */

set ACLUSER prod1 ;

/* Allow users and groups access to */
/* the domain again. */

modify ACL / LIBNAME
group1=(y,n,n,n)
group2=(y,n,n,n)
group3=(y,n,n,n)
group4=(y,n,n,n) ;

list ACL _all_ ;
run ;

ACL Special Users

SPD Server user IDs have two levels, 0 through 3 and 4 through 7. Level 4 through 7 user IDs can log in as an SPD Server 'super user' that can:
  • access any table
  • change table ACLs
  • disconnect users
  • perform administrative functions in a pinch
In many ways, SPD Server super users must be able to take on database administrator functions. The SPD Server super user cannot change the ownership of a table but they can assume the identity of the table owner to do required work. Often, this function happens in a pinch when a user needs access and the table owner or domain owner is out of the office.
The following should be considered when giving a user SPD Server super user status:
  • The user must be trusted, because SPD Server super users can access any data in any domain
  • How many SPD Server super users do you want? Limit the number in order to maintain control access.
  • SPD Server super users must be knowledgeable about the data and the database users' needs.
Assume the table user1_table1 is loaded, and only read permissions have been given to users in group1. User4 is a member of group4, and group4 does not have read access to the table. User1 is the owner of user1_table1 in domain d2. User1 is on vacation and user4 has been given an assignment which requires read access to the user1_table1 to create a report for management.
Management has approved user4 access to the table. The super user prod1 uses the ACLSPECIAL= option to modify the ACLs and to give user4 read access to the table.
     LIBNAME prod1d2 sasspds 'd2'
       server=zztop.5162
       user='prod1'
       password='spds123'
       aclspecial=YES
       IP=YES ;

     PROC SPDO library=prod1d2 ;

/* assign to the user to who owns   */
/* the ACL that will be modified    */

     set acluser user1 ;

/* give user ID 'user4' read access */
/* to user1_table1                  */

     modify ACL user1_table1 /
       user4=(y,n,n,n) ;
     list ACL _all_ ;
     quit;

Column-Level Security

The goal of column-level security is to allow only privileged users to access sensitive columns of tables that other users cannot read.
LIBNAME user1 sasspds 'onepath' server=zztop.5161 user='user1' 
       password='spds123';
LIBNAME user2 sasspds 'onepath' server=zztop.5161 user='user2' 
       password='spds123' aclgrp='group2';
LIBNAME user6 sasspds 'onepath' server=zztop.5161 user='user3' 
       password='spds123' aclgrp='group2';

/* generate some dummy data */
data user1.t;
id=1;
salary=2000;
run;

/* Example of only user2 in group2 */
/* being allowed to read column    */
/* salary                          */

PROC SPDO library=user1 ;

/* Assign who owns the ACLs */
set acluser;

/* Clean Up */
delete ACL t;
delete ACL t.salary;

/* Create an ACL on table t to     */
/* allow members of group2 to read */
/* table                           */

add ACL t;
modify ACL t / group2=(y,n,n,n);

/* Create an ACL on column t.salary*/
/* to only allow user2 of group2 to */
/* read the column                  */

add ACL t.salary;
modify ACL t.salary / group2=(y,n,n,n);
quit;

/* Let both users print the table */
/* Only user2 can access column    */
/* salary                           */

proc print data=user2.t;
run;

proc print data=user6.t;
run;

/* Example of every BUT user2 in */
/* group2 being allowed to read  */
/* column  salary               */

PROC SPDO library=user1 ;

/* Assign who owns the ACLs */
set acluser;

/* Clean Up Column ACL */
delete ACL t.salary;

/* Create an ACL on column t.salary*/
/* to only allow members of group2 to */
/* read the column                  */

add ACL t.salary;
modify ACL t.salary / user2=(y,n,n,n);


/* User permissions have priority over */
/* group permissions.  So now deny     */
/* user2 access to column salary       */

modify ACL t.salary / user2=(n,n,n,n);
quit;

/* Let both users print the table */
/* Only user6 can access column    */
/* salary                           */

proc print data=user2.t;
run;

proc print data=user6.t;
run;
quit;