This chapter describes access security, i.e. the system that limits access to IOC databases. It consists of the following sections:
The requirements for access security were generated at ANL/APS in 1992. The requirements document is:
EPICS: Channel Access Security - Functional Requirements, Ned D. Arnold, 03/-9/92.
This document is available through the EPICS website.
In order to “turn on” access security for a particular IOC the following must be done:
The following rules decide if access security is turned on for an IOC:
After an IOC has been booted with access security enabled, the access security rules can be changed by issuing the asSetFilename, asSetSubstitutions, and asInit. The functions asInitialize, asInitFile, and asInitFP, which are described below, can also be used.
Access security protects IOC databases from unauthorized Channel Access Clients. Access security is based on the following:
An IOC database can be accessed only via Channel Access or via the vxWorks or ioc shell. It is assumed that access to the local IOC console is protected via physical security, and that network access is protected via normal networking and physical security methods.
No attempt has been made to protect against the sophisticated saboteur. Network and physical security methods must be used to limit access to the subnet on which the iocs reside.
This document uses the following terms:
This section describes the format of a file containing definitions of the user access groups, host access groups, and access security groups. An IOC creates an access configuration database by reading an access configuration file (the extension .acf is recommended). Lets first give a simple example and then a complete description of the syntax.
These rules provide read access to anyone located anywhere and write access to user1 and user2 if they are located at host1 or host2.
In the following description:
The elements <name>, <user>, <host>, <pvname> and <calculation> can be given as quoted or unquoted strings. The rules for unquoted strings are the same as for database definitions.
Each IOC record contains a field ASG, which specifies the name of the ASG to which the record belongs. If this field is null or specifies a group which is not defined in the access security file then the record is placed in group DEFAULT.
The access privilege for a channel access client is determined as follows:
Multiple RULEs can be defined for a given ASG, even RULEs with identical levels and access permissions. The TRAPWRITE setting used for a client is determined by the first WRITE rule that passes the rule checks.
After creating or modifying an access configuration file it can be checked for syntax errors by issuing the command:
This is a Unix command. It displays errors on stdout. If no errors are detected it prints nothing. Only syntax errors not logic errors are detected. Thus it is still possible to get your self in trouble. The flag -S means a set of macro substitutions may appear. This is just like the macro substitutions for dbLoadDatabase.
In order to have access security turned on during IOC initialization the following command must appear in the startup file before iocInit is called:
If this command is not used then access security will not be started by iocInit. If an error occurs when iocInit calls asInit than all access to the ioc is disabled, i.e. no channel access client will be able to access the ioc. Note that this command does not read the file itself, it just saves the argument string for use later on, nor does it save the current working directory, which is why the use of an absolute path-name for the file is recommended (a path name could be specified relative to the current directory at the time when iocInit is run, but this is not recommended if the IOC also loads the subroutine record support as a later reload of the file might happen after the current directory had been changed).
Access security also supports macro substitution just like dbLoadDatabase. The following command specifies the desired substitutions:
This command must be issued before iocInit.
After an IOC is initialized the access security database can be changed. The preferred way is via the subroutine record described in the next section. It can also be changed by issuing the following command to the vxWorks shell:
It is also possible to reissue asSetFilename and/or asSetSubstitutions before asInit. If any error occurs during asInit the old access security configuration is maintained. It is NOT permissable to call asInit before iocInit is called.
Restarting access security after ioc initialization is an expensive operation and should not be used as a regular procedure.
Each database record has a field ASG which holds a character string. Any database configuration tool can be used to give a value to this field. If the ASG of a record is not defined or is not equal to a ASG in the configuration file then the record is placed in DEFAULT.
Two subroutines, which can be attached to a subroutine record, are available (provided with iocCore):
NOTE: These subroutines are automatically registered thus do NOT put a registrar definition in your database definition file.
If a record is created that attaches to these routines, it can be used to force the IOC to load a new access configuration database. To change the access configuration:
The following action is taken:
Each field of each record type has an associated access security level of ASL0 or ASL1. See the chapter “Database Definition” for details.
Lets design a set of rules for a Linac. Assume the following:
Most Linac IOC records will not have the ASG field defined and will thus be placed in ASG DEFAULT. The following records will have an ASG defined:
The following access configuration satisfies the above rules.
A brief summary of the Functional Requirements is:
Although the functional requirements doesn’t mention it, a fundamental goal is performance. The design provides almost no overhead during normal database access and moderate overhead for the following: channel access client/server connection, ioc initialization, a change in value of a process variable referenced by an access calculation, and dynamically changing a records access control group. Dynamically changing the user access groups, host access groups, or the rules, however, can be a time consuming operation. This is done, however, by a low priority IOC task and thus does not impact normal ioc operation.
Access security should be implemented as a stand alone system, i.e. it should not be imbedded tightly in database or channel access.
Within an IOC no access security is invoked. This means that database links and local channel access clients calls are not subject to access control. Also test routines such as dbgf should not be subject to access control.
It must be possible to easily define default access rules.
When an IOC is initialized, access security is optional.
The implementation provides a library of routines for accessing the security system. This library has no knowledge of channel access or IOC databases, i.e. it is generic. Database access, which is responsible for protecting an IOC database, calls library routines to add each IOC record to one of the access control groups.
Lets briefly discuss the access security system and how database access and channel access interact with it.
User access groups, host access groups, and access security groups are configured via an ASCII file.
The access security library consists of the following groups of routines: initialization, group manipulation, client manipulation, access computation, and diagnostic. The initialization routine reads a configuration file and creates a memory resident access control database. The group manipulation routines allow members to be added and removed from access groups. The client routines provide services for clients attached to members.
The interface between an IOC database and the access security system.
Whenever the Channel Access broadcast server receives a ca_search request and finds the process variable, it calls asAddClient. Whenever it disconnects it calls asRemoveClient. Whenever it issues a get or put to the database it must call asCheckGet or asCheckPut.
It is likely that the access rules will be defined such that many IOCs will attach to a common process variable. As a result the IOC containing the PV will have many CA clients.
What about password protection and encryption? I maintain that this is a problem to be solved in a level above the access security described in this document. This is the issue of protecting against the sophisticated saboteur.
Performance has not yet been measured but during the tests to measure memory usage no noticeable change in performance during ioc initialization or during Channel Access clients connection was noticed. Unless access privilege is violated the overhead during channel access gets and puts is only an extra comparison.
In order to measure memory usage, the following test was performed:
The memory consumption was measured before iocInit, after iocInit, after caput connected to all channels, and after caget connected to all 5000 channels. This was done for APS release 3.11.5 (before access security) and the first version which included access security. The results were:
|
R3.11.5 |
After |
Before iocInit |
4,244,520 |
4,860,840 |
After iocInit |
4,995,416 |
5,964,904 |
After caput |
5,449,780 |
6,658,868 |
After caget |
8,372,444 |
9,751,796 |
|
||
|
||
Before the database was loaded the memory used was 1,249,692 bytes. Thus most of the memory usage before iocInit resulted from storage for records. The increase since R3.11.5 results from added fields to dbCommon. Fields were added for access security, synchronous time support and for the new caching put support. The other increases in memory usage result from the control blocks needed to support access control. The entire design was based on maximum performance. This resulted in increased memory usage.
File asLib.h describes the access security data structures and the last section of this chapter has a diagram describing the relationship between the structures. The structures are:
All structures except ASGMEMBER and ASGCLIENT are created by the access security library itself when it reads an access security file. An ASGMEMBER is created each time asAddMember is called by code that interfaces to the database. An ASGCLIENT is created each time asAddClient is called by a channel access server.
The following are descriptions of arguments of routines described later.
These routines read an access definition file and perform all initialization necessary. The caller must provide a routine to provide input lines for asInitialize. asInitFile and asInitFP do their own input and also perform macro substitutions.
The initilization routines can be called multiple times. If an access system already exists the old definitions are removed and the new one initialized. Existing members are placed in the new ASGs.
The routines are called by code that knows how to associate ASG names with the database. In the case of IOC databases, dbCommon has a field ASG. At IOC initialization a call is made to asAddMember for every record instance in the IOC database.
This routine adds a new member to ASG asgName. The calling routine must provide storage for ASMEMBERPVT. Upon successful return ⋆ppvt will be equal to the address of storage used by the access control system. The access system keeps an orphan list for all asgNames not defined in the access configuration.
The caller must provide permanent storage for asgName.
This routine returns S_asLib_asNotActive without doing anything if access control is not active.
This routine removes a member from an access control group. If any clients are still present it returns an error status of S_asLib_clientExists without removing the member.
This routine returns S_asLib_asNotActive without doing anything if access control is not active.
For each member, the access system keeps a pointer that can be used by the caller. This routine returns the value of the pointer.
This routine returns NULL if access security is not active
This routine is used to set the pointer returned by asGetMemberPvt.
This routine returns S_asLib_asNotActive without doing anything if access control is not active.
This routine changes the group for an existing member. The access rights of all clients of the member are recomputed.
The caller must provide permanent storage for newAsgName.
This routine returns S_asLib_asNotActive without doing anything if access control is not active.
This code is called by a channel access server.
This routine adds a client to an ASG member. The calling routine must provide storage for the ASCLIENTPVT pointer. ASMEMBERPVT is the value that was set by calling asAddMember. The database code and the server code must develop a convention that allows the server code to locate the ASMEMBERPVT. For IOC databases, ASMEMBERPVT is kept in dbCommon. asl is the access security level.
The caller must provide permanent storage for user and host. Note that user is “const char *” but host is just “char *”. The reason is the host names are converted to lower case.
This routine returns S_asLib_asNotActive without doing anything if access control is not active.
This routine changes one or more of the values asl, user, and host for an existing client. Again the caller must provide permanent storage for user and host. It is permissible to use the same user and host used in the call to asAddClient with different values.
This routine returns S_asLib_asNotActive without doing anything if access control is not active.
This call removes a client.
This routine returns S_asLib_asNotActive without doing anything if access control is not active.
For each client, the access system keeps a pointer that can be used by the caller. This routine returns the value of the pointer.
This routine returns NULL if access security is not active.
This routine is used to set the pointer returned by asGetClientPvt.
This routine registers a callback that will be called whenever the access privilege of the client changes.
This routine returns S_asLib_asNotActive without doing anything if access control is not active.
This routine, actually a macro, returns TRUE if the client has read access rights.
This routine, actually a macro, returns TRUE if the client has write access rights.
These routines must be called before and after any write performed for a client, to permit any registered listeners to be notified. The value returned by the call to asTrapWriteBefore is the trapPvt value that must subsequently be passed to the asTrapWriteAfter routine. The serverSpecific argument is assigned to the serverSpecific field of the asTrapWriteMessage described below.
This routine calls asComputeAsg for each access security group.
This routine returns S_asLib_asNotActive without doing anything if access control is not active.
This routine calculates all CALC entries for the ASG and calls asCompute for each client of each member of the specified access security group.
This routine returns S_asLib_asNotActive without doing anything if access control is not active.
rights
This routine computes the access rights of a client. This routine is normally called by the access library itself rather than user code.
This routine returns S_asLib_asNotActive without doing anything if access control is not active.
These routines print the current access security database. If verbose is 0 (FALSE), then only the information obtained from the access security file is printed.
If verbose is TRUE then additional information is printed. The value of each INP is displayed. The list of members belonging to each ASG and the clients belonging to each member are displayed. If member callback is specified as an argument, then it is called for each member. If client callback is specified, it is called for each access security client.
These routines display the specified UAG or if uagname is NULL each UAG defined in the access security database.
These routines display the specified UAG or if uagname is NULL each UAG defined in the access security database.
These routines display the rules for the specified ASG or if asgname is NULL the rules for each ASG defined in the access security database.
This routine displays the member and, if clients is TRUE, client information for the specified ASG or if asgname is NULL the member and client information for each ASG defined in the access security database. It also calls memcallback for each member if this argument is not NULL.
These show the contents of the hash table used to locate UAGs and HAGs,
The definition of access level means that a level is defined for each field of each record type.
The meanings of the Access Security Level definitions are as follows:
Most record types assign ASL as follows: The fields VAL, RES (Reset), and CMD use the value ASL0. All other fields use ASL1.
struct dbCommon contains the fields ASG and ASP. ASG (Access Security Group) is a character string. The value can be assigned via a database configuration tool or else a utility could be provided to assign values during ioc initialization. ASP is an access security private field. It contains the address of an ASGMEMBER.
Two files asDbLib.c and asCa.c implement the interface between IOC databases and access control. They contain the following routines:
Calling this routine sets the filename of an access configuration file, but does not save the current working directory, so the use of an absolute pathname is strongly recommended. The next call to asInit uses this filename. asSetFilename must be called before iocInit otherwise access configuration is disabled. Is access security is disabled during iocInit it will never be turned on.
This routine specifies macro substitutions for use while reading the configuration file.
This routines call asInitialize. If the current access configuration file, as specified by asSetFilename, is NULL then the routine just returns, otherwise the configuration file is used to create the access configuration database. After initialization all records in the database are made members of the appropriate access control group.
asInit is called by iocInit, and can also be called after iocInit to change the access configuration information.
asInitAsyn spawns a task asInitTask to perform the initialization. This allows asInitAsyn to be called from a subroutine called by the process entry of a subroutine record. asInitTask calls taskwdInsert so that if it suspends for some reason taskwd can detect the failure.
If the caller provides an ASDBCALLBACK then when either initialization completes or taskwd detects a failure the user’s callback routine is called via one of the standard callback tasks.
asInitAsyn will return a value of -1 if access initialization is already active. It returns 0 if asInitTask is successfully spawned.
Get Access Security level for the field referenced by a database access structure. The argument is defined as a void⋆ so that both old and new database access can be used.
Get ASMEMBERPVT for the field referenced by a database access structure. The argument is defined as a void⋆ so that both old and new database access can be used.
This is a routine to test asAddClient. It simulates the calls that are made by Channel Access.
These routines are provided so that a channel access client can force an ioc to load a new access configuration database.
These are routines that can be attached to a subroutine record. Whenever a 1 is written to the record, asSubProcess calls asInit. If asInit returns success, it returns asynchronously. When asInitTask calls the completion routine supplied by asSubProcess, the completion status is used to determine whether to place the record in alarm or not.
These routines provide interfaces to the asDump routines described in the previous chapter. They do NOT lock before calling the associated routine. Thus they may fail if the access security configuration is changing while they are running. However the danger of the user accidently aborting a command and leaving the access security system locked is considered a risk that should be avoided.
These routines call asDumpFP with a member callback and with verbose TRUE.
These routines call asDumpUagFP.
These routines call asDumpHagFP.
These routines call asDumpRulesFP.
These routines call asDumpMemFP.
EPICS Access Security was originally designed to protect Input Output Controllers (IOCs) from unauthorized access via the Channel Access (CA) network protocol. It can also be used by any Channel Access Server (CAS) tool. For example the Channel Access PV Gateway implements its own access security. This section describes the interaction between a CA server and the Access Security system. It also briefly describes how the current access rights state is communicated to clients of the EPICS control system via the CA client interface.
The CA server calls asAddClient() and asRegisterClientCallback() for each of the channels that a client connects to the server. The routine asRemoveClient() is called whenever the client clears (removes) a channel or when the client disconnects.
The server maintains storage for the clients host and user names. The initial value of these strings are supplied to the server when the client connects and can be updated at any time by the client. When these strings change then asChangeClient() is called for each of the channels maintained by the server for the client.
The server checks for read access when processing gets and for write access when processing puts. If access is denied an exception message will be sent to the client. The macros asCheckGet() and asCheckPut() perform the checks.
The server checks for read access when processing requests to register an event callback (monitor) for the client. If there is read access the server always sends an initial update indicating the current value. If there isn’t read access the server sends one update indicating no read access and disables subsequent updates.
The server registers a callback with asRegisterClientCallback() in order to receives asynchronous notification of access rights changes. When a channel’s access rights change, the server communicates the current state to the client library. If read access to a channel is lost and there are events (monitors) registered on the channel then the server sends an update to the client for each of them indicating no access and disables future updates for each event. If read access is reestablished to a channel and there are events (monitors) registered on the channel, the server reenables updates and sends an initial update message to the client for each of them.
The server must also call asTrapWriteBefore() and asTrapWriteAfter() before and after a put request from a client is performed.
Additional details on the channel access client side callable interfaces to access security can be obtained from the Channel Access Reference Manual.
The client library stores and maintains the current state of the access rights for each channel that it has established. The client library receives asynchronous updates of the current access rights state from the server. It uses this state to check for read access when processing gets and for write access when processing puts. If a program issues a channel access request that is inconsistent with the client library’s current knowledge of the access rights state, the access is denied and an error code is returned to the application. The current access rights state as known by the client library can be tested by an applications program with the C macros ca_read_access() and ca_write_access().
An application program can also receive asynchronous notification of changes to the access rights state by registering a function to be called whenever the client library updates its knowledge of the access rights state. The application’s callback function is installed using ca_replace_access_rights_event().
If the access rights state changes in the server after a request is queued in the client library but before the request is processed by the server, it is possible that the request will fail in the server. Under these circumstances then an exception will be raised in the client.
The server always sends one update to the client when the event (monitor) is initially registered. If there isn’t read access then the status in the arguments to the application program’s event call back function indicates no read access and the value in the arguments to the clients event call back is set to zero. If the read access right changes after the event is initially registered, another update is supplied to the application programs call back function.
Access security provides a facility asTrapWrite that can monitor write requests and pass them to any software that registers a listener function. In order to use this facility three things are necessary:
The remainder of this section describes how a facility can use asTrapWrite.h, which is defined as:
After a facility calls asTrapWriteRegisterListener() its asTrapWriteListener() will get called before and after each write with an associated RULE that has the option TRAPWRITE set.
asTrapWriteRegisterListener() is passed the address of an asTrapWriteMessage. This message contains the following fields:
asTrapWriteListener delays the associated server thread so it must not do anything that causes it to block.
The IOC’s RSRV server the calls asTrapWriteBefore with serverSpecific set to a dbChannel ⋆ describing the PV.
This section provides a few aids for reading the access security code. Include file asLib.h describes the control blocks used by the access security library.
The following files form the access security system:
Definitions for the portion of access security that is independent of IOC databases.
Definitions for access routines that interface to an IOC database.
Lex and Yacc (actually EPICS flex and antelope) are used to parse the access configuration file. This is the lex input file.
This is the yacc input file. Note that it includes asLibRoutines.c, which do most of the work.
These are the routines that implement access security. This code has no knowledge of the database or channel access. It is a general purpose access security implementation.
This contains the code for interfacing access security to the IOC database.
This code contains the channel access client code that implements the INP and CALC definitions in an access security database.
The Unix program which performs a syntax check on a configuration file.
Because it is possible for multiple tasks to simultaneously modify the access security database it is necessary to provide locking. Rather than try to provide low level locking, the entire access security database is locked during critical operations. The only things this should hold up are access initialization, CA searches, CA clears, and diagnostic routines. It should NEVER cause record processing to wait. In addition CA gets and puts should never be delayed. One exception exists. If the ASG field of a record is changed then asChangeGroup is called which locks.
All operations invoked from outside the access security library that cause changes to the internal structures of the access security database.routines lock.