Main Page | Features | Central Services | csv-Files | Types | Transfer | Access | API-C | API-.NET | API-Java | Examples | Downloads
page generated on 05.05.2024 - 04:45
Security

Introduction

Security is increasingly important issue in this day and age. In a control system there are two issues at play.

One is to of course prevent attack from mischief makers. This relates to the security we all know and love, but has more to do with system administration than to the control system proper.

The second is to prevent accidental inteference with control system operations. So the control system instructions themselves should incorporate a security system to prevent this from happening.

TINE allows open read access to all control parameters (unless 'exclusive READ' is in play

  • see below). Write access, however, can be restricted to a set of users and/or specific networks.

Unintended Security

Merely browsing the control system parameters might in itself not lead to useful information. However, the Property names and device names might be so obscure as to be useless to anyone but the person who dreamed them up. Similarly if a server developer requires unorthodox behavior from a client before reacting (call Property "A" twice and then call Property "B" with the unregistered device name "X", etc.) the server also becomes next to useless to anyone but the server developer.

Such servers offer an 'unintended' security, since only the server developer is familiar with their behavior and is therefore capable of writing client applications.

This is in general a bad practice and is frowned upon for a number of reasons. The first is there is no reason to do it. TINE offers enough security features so that such programming styles are completely unwarranted. Another reason is that it is generally inevitable that other client applications developers will need parameters from such servers as well. Yet another reason is that such tactics defeat the entire purpose of having a human understandable control system developing environment.

Security by User ID

Screening server commands requiring WRITE access against a user list is the simplest and most effective way of avoiding an accidental change of server settings. It is also a 'soft' form of security, meaning that it would be in principle very easy to cheat.

All TINE contracts contain the user name of the caller in the incoming header packet. This user name is determined in different ways depending on the platform of the caller. For Windows and UNIX systems standard OS API calls are used to determine the logged in user. Other legacy platforms might make use of an environment variable (e.g. "USERNAME"). Thus if a mischief maker wanted to 'become' another user he could always create a local user account with the appropriate user name on his own host machine and go from there.

That is why we say that this is 'soft' security designed to prevent accidental changes of system settings from persons who didn't intend to make such changes in the first place.

Device Server Access

A TINE server has by default open WRITE access to all users unless a 'users' access list has been applied to the device server in question. Such an access list can be applied via API call (see AppendRegisteredUsers()) or via a local database file called 'users.csv', containing a list of all users to be granted WRITE access. An example 'users.csv' is shown below:

USERNAME
HERA_CONSOLE
SMITH
JONES

As an alternative, including a tag <USERS_ALLOWED> withing a fec.xml configuration file will have the same effect, as will making use of the API call AppendRegisteredUserList().

This file can reside directly in the FEC_HOME directory, in which case it provides the default users access control list for all registered equipment modules on the FEC process, or it can reside in the equipment module subdirectory (under FEC_HOME) whose directory name is given by the equipment module's local name. In the latter case, the access control list applies only to the equipment module in question. Of course, in those cases where a FEC contains only a single equipment module (a typical case) then there is no difference where this file is located.

The members of a specific group can also be give WRITE access via the 'users' access list.
The group specified can either be a 'local' or 'network' group. To specify a group the 'user name' entered should be of the form "<domain:group>".

A windows server might for instance have a users.csv file which contains

USERNAME
Smith
Jones
<win:mhfe_user>

In such a case, the specific users "Smith" and "Jones" will be allowed WRITE access as well as all members of the group "mhfe_user" on the "win" domain.

On a Unix server, any 'domain' will trigger a network scan (of the nis/ldap/local) group.

If the 'domain' is omitted, then a specific file called "mhfe_user-members.csv" (of the same format at 'users.csv') will be scanned for the group membership information. If such a file does not exist then a network scan will ensue. On a windows machine the domain used will be taken from the "USERDOMAIN" environment variable. A Unix machine will (as before) do a network scan based on the known group hierarchy.

Property-specific Access

In addition, individual registered properties can be assigned their own user access lists. This can be achieved by supplying a users file of the same format as above within an equipment module's configuration repository, but with the specific name <property>-users.csv where <property> is the name of the registered property to receive the access list. Alternatives include using the <USERS_ALLOWED> tag within the <PROPERTY> section of a fec.xml file or making use of the API call AssignPropertyAccessList().

A special case is a file with the specific name 'property-default-users.csv'. If this file is found within an equipment module's subdirectory repository, then it provides a 'default' access list for all properties. This provides a simple mechanism using configuration files for providing 'most' properties with the same security access except for instance for a single property.

As in the general device server access case, 'groups' can also be entered in the property specific users case.

Device-specific Access

Likewise, individual registered devices can be assigned their own user access lists. This can be achieved by supplying a users file of the same format as above within an equipment module's configuration repository, but with the specific name <device>-users.csv where <device> is the name of the registered device to receive the access list. Alternatives include using the <USERS_ALLOWED> tag within the <DEVICE> section of a fec.xml file or making use of the API call AssignDeviceAccessList().

A special case is a file with the specific name 'device-default-users.csv'. If this file is found within an equipment module's subdirectory repository, then it provides a 'default' access list for all properties. This provides a simple mechanism using configuration files for providing 'most' devices with the same security access except for instance for a single device.

As in the general device server access case, 'groups' can also be entered in the device specific users case.

Important:

Note that if a server process is also a client to other servers, its 'user name' will be its FEC name, regardless of the user logged in running the process. There are serveral reasons that this is so. For one, commands inluding the name of the caller are logged by default in a server's log file. So it is easy to establish whether a command is originating from a logged in user running an application or from a FEC process. Likewise, a middle layer server process can be given WRITE access regardless of who the logged in user is!

Dynamic Changes

A single configuration file or API call during initialization will provide a static base for WRITE access to a TINE server. Occasionally, there arises the need to modify the access lists during runtime. This is primarily achieved by making use of the Stock Properties "ADDUSER" and "DELUSER". Both of these stock properties can accept a list of names using a format such as CF_NAME16, etc. So based on relevant conditions or states a reduced or exanding list of allowed users can be applied to the server during run time.

A special feature of the stock property "ADDUSER" allows an 'empty' input set, i.e. a call to "ADDUSER" with WRITE access but no data input. This forces a re-read of the users.csv data file. Hence an easy way to 'swap' sets of users is to first swap users.csv files and then issue the "ADDUSER" command.

It is important to remember that both of these stock properties themselves require WRITE access, so that any user utilizing these properties must remember to ensure that he remains an 'allowed' user.

Security by Network ID

A much harder security criterion is the network address of the caller. TINE servers can restrict WRITE access to callers residing on a particular IP subnet or even a particular IP address (or set of addresses and subnets).

A TINE server has by default open WRITE access to all networks unless a network address access list has been provided for the device server in question. Such an access list can be applied via an API call (see AppendRegisteredNetsList()), or via a local database file called 'ipnets.csv', containing a list of all network addresses to be granted WRITE access. An example 'ipnets.csv' is shown below:

SUBNET
131.169.150.255
131.169.121.255
131.169.120.255
131.169.110.255
131.169.9.255

If an entry's last byte ends in 255, this will allow callers coming from the entire subnet domain WRITE access. Otherwise the caller's IP address must match an entry in the table. The ip address access list refers to all device servers attached to the FEC process and can currently not be applied to specific properties, etc.

In addition, specific subnet domains can be assigned WRITE access but making use of the CIDR (Class Inter Domain Routing) notation. For instance an entry such as

192.168.5.0/26

treats the first 26 bits of an address as a subnet and in the above case would allow 192.168.5.0 -> 192.168.5.63 access.

192.168.5.64/26

would allow 192.168.5.64 -> 192.168.5.127 access, etc.

As in the case for the users access lists, this file can reside directly in the FEC_HOME directory, in which case it provides the default network access control list for all registered equipment modules on the FEC process, or it can reside in the equipment module subdirectory (under FEC_HOME) whose directory name is given by the equipment module's local name. In the latter case, the access control list applies only to the equipment module in question.

Property-specific Access

Individual registered properties can also be assigned their own network access lists. This can be achieved by supplying an ipnets file of the same format as above within an equipment module's configuration repository, but with the specific name <property>-ipnets.csv where <property> is the name of the registered property to receive the access list. Alternatives include using making use of the API call AssignDeviceAccessList().

A special case is a file with the specific name 'property-default-ipnets.csv'. If this file is found within an equipment module's subdirectory repository, then it provides a 'default' access list for all properties. This provides a simple mechanism using configuration files for providing 'most' properties with the same security access except for instance for a single property.

Device-specific Access

Likewise, individual registered devices can be assigned their own network access lists. This can be achieved by supplying a ipnets file of the same format as above within an equipment module's configuration repository, but with the specific name <device>-ipnets.csv where <device> is the name of the registered device to receive the access list. Alternatives include making use of the API call AssignDeviceNetsList().

A special case is a file with the specific name 'device-default-users.csv'. If this file is found within an equipment module's subdirectory repository, then it provides a 'default' access list for all properties. This provides a simple mechanism using configuration files for providing 'most' devices with the same security access except for instance for a single device.

Dynamic Changes

As in the case for the 'users' access list, specific network addresses can be added dynamically at runtime by making use of the stock properties "ADDIPNET" and likewise removed through "DELIPNET". These stock properties work in a similar way to "ADDUSER" and "DELUSER" described above.

Important:

When a client runs on the same machine as the server, it will likely appear to the server to have the "IP" address "0.0.0.0" as a 'pipe' plus 'shared memory' will be used for communication in lieu of the loopback. This is not necessarily true as a java server will not be able to communicate to a native windows client via the shared memory, etc. It is nonetheless a good idea to include the address "0.0.0.0" for just such eventuallities.

If both a 'users.csv' file and an 'ipnets.csv' file are in play, then the security is additive. The caller must be on the users list and must come from a subnet or IP address on the ipnets list.

If IPX is in use, the same network security petains to IPX networks. Namely, if there is an 'ipxnets.csv' local database file containing the allowed IPX networks, then the caller must come from one of those networks.

Security by Access Locks

TINE offers a systematic access control which goes beyond the usual security measures. Namely, TINE offsers Access Locks. In general, all TINE servers have a built-in stock property called "ACCESSLOCK" with the following behavior.

A caller can at any time query the server to see if any other caller has an Access Lock on the server. If not, the caller (if he has WRITE access to begin with) can set an Access Lock, so that only commands from the caller from his current calling process are allowed. This is frequently the preferred solution for those console applications which have considerable control over a set of control elements and should therefore only run once at one station, the danger being that accidently starting the same console application at a second station could compromise control procedures and wreak general havoc. If the console application acquires an access lock, then any secondary process will not be able to change anything!

Access locks can be either pre-emptive or persistent. A pre-emptive lock can be aborted by another caller, who must specifically request a 'release' of the access lock. A presistent access lock cannot be aborted, except by the same caller at the same station requesting the release. Should the caller and or station suddenly vanish or otherwise not release the access lock, then the access lock will remain in effect until the lock duration has expired (the maximum duration being 60 seconds). Persistent access locks will automatically renew the lock before the maximum duration expires. The calling program should then free the access lock when it is no longer needed. See SetAccessLock() for the C API prototype. An example in C is shown below:

/* acquire a persistent lock (persistent lock will ignore the duration parameter) */
cc = SetAccessLock("DESY2","PiControls",LOCK_PERSISTENT,0);
if (cc != 0) dbglog("SetAccessLock : %s",erlst[cc]);
/* do something important ... */
/* release the lock */
FreeAccessLock("DESY2","PiControls");

In java, setAccessLock() is a static method of the TQuery class:

// acquire a persistent lock
int cc = TQuery.setAccessLock("DESY2","PiControls", TLinkFactory.AccessLockType.LOCK_PERSISTENT, 60);
if (cc != 0)
{
System.out.println("setAccessLock: "+TErrorList.getErrorString(cc));
}
// do something important ...
// release the lock
TQuery.removeAccessLock("DESY2","PiControls");

If a preemptive lock has been obtained (LOCK_PREEMPTIVE), attempts to issue WRITE commands will fail as before from a client which does not own the lock. However in this case, a client can obtain the lock by first calling SetAccessLock() using the LOCK_ABORT flag and then obtaining the lock for himself. If the lock is persistent (LOCK_PERSISTENT) then any attempt to abort the lock by a non-owner will fail with 'access denied'. A caller can determine if an access lock exists and, if so, who has it by calling GetAccessLockInformation(). As an example (C API):

int cc;
NAME32 callerName,callerIp,timeLeft;
cc = GetAccessLockInformation("TEST","LxSineServer",&callerName,&callerIp,&timeLeft);
if (cc == 0)
{
dbglog("LxSineServer locked by %s at address %s (%s)",callerName.name,callerIp.name,timeLeft.name);
}

In java, getAccessLockInformation() is a static method of the TQUery class.

String[] lck = TQuery.getAccessLockInformation("DESY2","PiControls");
System.out.println("PiControls locked by "+lck[0]+" at address "+lck[1]+" ("+lck[2]+")");

In addition, a server can also require a lock in order to process a command! Before the server's equipment module dispatch routine (i.e. property handler in java) is called, the CA_LOCKED access bit is set if the caller has obtained an access lock. If certain properties require the caller to first obtain an access lock and the CA_LOCK bit is not set, then the server's equipment module (property handler) should return 'not_locked' to inform the caller. This is in analogy with checking the CA_WRITE access bit as shown below.

Requiring Write Access

Of course, in order for WRITE access to play a role, the server developer must require WRITE access of the caller in order to change settings.

In the case of the 'Buffered server' this is all taken care of by the buffered server engine as long as the property is exported with the CA_WRITE access bit, indicating that any input data sent to the property must ask for WRITE permission.

In normal, full-functioned, TINE servers this cross-check must be supplied by the server developer. If the server code began as 'wizard-generated' code, then these cross-checks were supplied in the initial code.

In essence, this amounts to wrapping all properties which change settings with a check on the access flag coming in with the call. In 'C' this might look something like:

int bpmeqm(char *devName,char *devProperty,DTYPE *dout, DTYPE *din,short access)
{
int devnr,prpid,i,cc;
short l_online;
if ((devnr = GetDeviceNumber(BPMEQM_TAG,devName)) < 0) return illegal_equipment_number;
prpid = GetPropertyId(BPMEQM_TAG,devProperty);
switch (prpid)
{
case PRP_ONLINE:
if (access&CA_WRITE)
{
if (din->dArrayLength > 0)
{
if (din->dArrayLength > PRP_ONLINE_SIZE) return dimension_error;
if ((cc=getValuesAsShort(din,&l_online,PRP_ONLINE_SIZE)) != 0) return cc;
if (l_online > PRP_ONLINE_UPR_LIMIT) return out_of_range;
if (l_online < PRP_ONLINE_LWR_LIMIT) return out_of_range;
g_online = l_online;
}
}
if (dout->dArrayLength > 0)
{
if (dout->dArrayLength > PRP_ONLINE_SIZE) return dimension_error;
if ((cc=putValuesFromShort(dout,&g_online,PRP_ONLINE_SIZE)) != 0) return cc;
}
return 0;
case PRP_ORBIT_Y:
...
}

In 'Visual Basic' this might look something like:

Sub BpmeqmEqpFcn(srvControl As Srv, ByVal devName As String, ByVal devProperty As String, ByVal outArrayLen As Long, ByVal inArrayLen As Long, ByVal devAccess As Integer)
Dim i As Integer
Dim devNr As Integer
Dim lclOnline As Integer
devNr = srvControl.EqpGetModuleNumber(devName)
If devNr < 0 Then
srvControl.EqpSetCompletion illegal_equipment_number, ""
Exit Sub
End If
Select Case devProperty
Case "ONLINE"
If (devAccess And CA_WRITE) = CA_WRITE Then
If inArrayLen = 0 Then
srvControl.EqpSetCompletion illegal_read_write, ""
Exit Sub
End If
' Get the incoming data:
srvControl.EqpRecvData lclOnline
' Do range checking:
If lclOnline > PRP_ONLINE_UPRLIMIT Then
srvControl.EqpSetCompletion out_of_range, ""
Exit Sub
End If
If lclOnline < PRP_ONLINE_LWRLIMIT Then
srvControl.EqpSetCompletion out_of_range, ""
Exit Sub
End If
' Copy incoming data into global data buffer
prpOnline = lclOnline
End If
If outArrayLen > 0 Then
srvControl.EqpSendData prpOnline
End If
...

And in java, this might look something like:

// assume that 'callFreuency' is the registered property handler for property "Frequency"
private int callFrequency(String devName, TDataType dout, TDataType din, TAccess devAccess)
{
int cc = 0;
SineDevice theDevice;
if (devAccess.isWrite())
{ // CLIENT WANTS TO SET FREQUENCY (allow single-channel write)
double[] input = new double[1];
if (din.getArrayLength() != 1) return TErrorList.dimension_error;
theDevice = (SineDevice) myDeviceSet.getDevice(devName);
if (theDevice == null) return TErrorList.illegal_equipment_number;
if ((cc=din.getData(input)) != 0) return cc;
theDevice.setFrequency(input[0]);
}
if (devAccess.isRead())
{ // CLIENT WANTS TO READ (allow multi-channel read)
int nret = dout.getArrayLength();
if (nret < 1) return TErrorList.dimension_error;
int ndv = myDeviceSet.getNumberOfDevices();
int dv = myDeviceSet.getDeviceNumber(devName);
if (dv < 0 || dv >= ndv) return TErrorList.illegal_equipment_number;
double[] output = new double[ndv];
for (int i=0; i<ndv; i++)
{
theDevice = (SineDevice) myDeviceSet.getDevice(i);
output[i] = theDevice.getFrequency();
}
cc = dout.putData(output,nret,dv);
}
return cc;
}

In any case, it is a necessary element of the overall security. If this cross-check is omitted, then a simple READ access call could end up changing settings, which could easily happen by accident when browsing the control system elements.

One final note concerning security: TINE also offers 'exclusive' read access for any properties registered with the exclusive read access bit set (CA_XREAD). This can be supplied in calls to RegisterPropertyInformation() (for example) or in configuration files which register properties (e.g. exports.csv, in which case the string "XREAD" should be supplied). If CA_XREAD is supplied, but CA_READ isn't, then the property in question will traverse all security rules applicable to WRITE commands before accepting the request. If both CA_XREAD and CA_READ are supplied, then the exclusive read is not applied to a request unless an ACCESS LOCK is in force.

GetAccessLockInformation
TINE_EXPORT int GetAccessLockInformation(char *context, char *server, NAME32 *callerName, NAME32 *callerIp, NAME32 *timeLeft)
Acquires access lock information from the server specified.
Definition: client.c:10342
GetDeviceNumber
TINE_EXPORT int GetDeviceNumber(char *eqm, char *devname)
Gives the registered device number for the specified device name.
Definition: srvdbase.c:7752
NAME32::name
char name[32]
Definition: tinetype.h:254
NAME32
Defines a TINE 32-character fixed-length string data object.
Definition: tinetype.h:252
TErrorList
TErrorList
Definition: errors.h:74
FreeAccessLock
TINE_EXPORT int FreeAccessLock(char *context, char *server)
Frees an access lock on the server specified.
Definition: client.c:9725
DTYPE
Defines a TINE data object.
Definition: tinetype.h:996
out_of_range
@ out_of_range
Definition: errors.h:119
illegal_equipment_number
@ illegal_equipment_number
Definition: errors.h:115
SetAccessLock
TINE_EXPORT int SetAccessLock(char *context, char *server, int lockType, int lockDuration)
Acquires an access lock to the server specified.
Definition: client.c:10336
dimension_error
@ dimension_error
Definition: errors.h:102
GetPropertyId
TINE_EXPORT int GetPropertyId(char *eqm, char *prpName)
Gives the associated property identifier for the given property name.
Definition: srvdbase.c:7784
DTYPE::dArrayLength
UINT32 dArrayLength
Definition: tinetype.h:998

Impressum   |   Imprint   |   Datenschutzerklaerung   |   Data Privacy Policy   |   Declaration of Accessibility   |   Erklaerung zur Barrierefreiheit
Generated for TINE API by  doxygen 1.5.8