📜 ⬆️ ⬇️

Recommendations for installing InterSystems Caché DBMS in product mode

This article was written as an attempt to share experience on installing InterSystems Caché DBMS in a product mode. Everyone knows that the configuration of the DBMS for development is very different from the combat conditions. If the development is usually carried out in greenhouse conditions with minimal security settings, then when we post our project on the live Internet, then we should take maximum care of its reliable and uninterrupted existence in an aggressive environment.

InterSystems Caché DBMS Installation Procedure in Advanced Security

OS security settings


You need to start with the operating system, you must do the following:
  • Minimize the rights of the Caché DBMS technology account
  • Rename the local computer administrator account.
  • Leave only the minimum required users in the OS.
  • Timely install security updates for the operating system and used services.
  • Use and regularly update anti-virus software.
  • Disable or remove unused services and services.
  • Restrict access to database files
  • Restrict rights to Caché data files (keep only with the owner and database administrators)
  • For UNIX / Linux systems, you need to create the following types of groups and users that you need to create before starting the installation:
    • Owner user ID
    • Management group ID
    • Internal Caché system group ID
    • Internal Caché user ID


InterSystems Caché installation time security settings


The manufacturer of the DBMS company InterSystems strongly recommends that you deploy application solutions only on versions not lower than Caché 2015.2.

During installation, you must perform the following steps:


  • Select the installation mode Locked down (Elevated)
  • Select the Custom Setup installation mode and select only those components that are minimally necessary for the application to work.
  • When installing, specify the SuperServer port, which is different from the standard TCP port 1972 settings
  • When installing, specify the port of the internal web server that is different from the standard TCP settings for port 57772
  • Set the path to the Caché instance to a different path from the standard path (for Windows systems, the default path is C: \ lnterSystems \ Cache, for UNIX / Linux systems - / usr / Cachesys

Caché security settings after installation


After installation, the following actions should be performed (most of them are performed during the Locked down installation mode:


All services and resources that are not used by the application systems of the certified solution are disabled.

For services using network access, IP addresses must be explicitly specified from which remote interaction is possible.

Unused CSP web applications should be disabled.
')
For the necessary CSP applications, access to them without authentication and authorization should be excluded.

Must be password protected and access to the CSP Gateway is restricted.

The audit of the DBMS operation must be enabled.

Encryption option must be enabled for the configuration file.

As a security check, the system settings should be launched from the Security Advisor management portal and made recommendations on the results of its work.

[Home] -> [System]> [Security Management]> [Services]
For services (Services section):

Ability to set% globals should not be turned off - The ability to change percentage globals should be turned off . Since such globals are often used to operate the system code, changes to such variables can lead to unpredictable consequences.

Unauthenticated should be off - Unauthenticated access should be turned off. Unauthenticated access to the service gives all users access to it.

Service should be disabled unless required - If the service is not used, it should be disabled. Access to any service that is not used by the application can provide an unjustified level of access to the system.

Service should use Kerberos authentication - Access through any other authentication mechanism does not provide the maximum level of security

Service should have client IP addresses assigned - IP addresses of connections to the service must be explicitly specified. Restricting the IP addresses from which connections will be taken allows you to more tightly control connections to Caché

Service is Public - Public services enable all users, including the UnknownUser account that does not require authentication, to receive unregulated access to Caché

[Home] -> [System]> [Security Management]> [Web Applications]
Applications (CSP, Privileged Routine, and Client Applications)



Application is Public - Public applications enable all users, including the UnknownUser account that does not require authentication, to receive unregulated access to Caché

Application conditionally grants the% AII role - A system cannot be considered secure if the application potentially has the ability to delegate all privileges to its users. Applications should not delegate all privileges.

Application grants the% AII role - The application explicitly delegates all privileges to its users. Applications should not delegate all privileges.

1. User management



1.1 Managing System Accounts



You must make sure that unused system accounts are blocked or removed, and the password is changed for the system accounts you use.

To identify such accounts, you must use the Security Advisor management portal component. To do this, go to the management portal at [Home]> [Security Management]> [Security Advisor] and for the entries in the Users section for which Recommendations = "Password should be changed from the default password", the passwords should be changed for the corresponding users .



1.2 Privileged Account Management



If the DBMS is administered by several people, then a personal account must be created for everyone, endowed with the minimum necessary privileges for the performance of official duties.

1.3 Management of rights and privileges



Access control must be guided by the principle of minimal privileges. That is, to prohibit the user everything, and then provide only the rights necessary for the performance of official duties. At assignment of privileges it is necessary to follow the rule of granting privileges only by roles. That is, the rights to assign only for the role, and not for a specific user, then assign the role to a specific user.

1.4 Differentiation of access rights



To check the security settings in terms of access control, run Security Advisor. The following steps are required depending on the recommendations issued by the Security Advisor.

For roles (Roles):



This role holds privileges on the Audit database - This role has privileges to access the audit database. Access to reading allows you to use audit data in an inappropriate manner. Write access allows you to compromise audit data

This role holds the% Admin_Secure privilege - This role includes the% Admin_Secure role, which allows changing access privileges for other users /

This role holds WRITE privilege on the CACHESYS database - This role gives the right to write to the CACHESYS system database, which allows you to change the system code and Caché system settings

For users (Users):



At least 2 and at most 5 users should have the% AII role - at least 2 and no more than 5 users should have the role of% All. Too few users with this role can lead to access problems in emergency situations; too many users may compromise overall system security.

This user holds the% AII role - This user has the role of% All. It is necessary to check the necessity of assigning this role to this user.

UnknownUser account should not have the% AII role - The system cannot be considered secure if UnknownUser has the role of% All.

Account has never been used - The account has never been used . Unused accounts can be used when trying to gain unauthorized access.

Account appears dormant and should be disabled - The account is inactive and must be disabled. Inactive accounts (those that have not been used for 30 days) may be used when attempting to gain unauthorized access.

Password should be changed from default password - the default value for the password should be changed.

When deleting a user, you must ensure that the roles and privileges created by this user are deleted if they are not required.

1.5 Setting a password policy



Support for case sensitivity is enabled by default in Caché.

Password policy is implemented through the system management portal in the section:
[Home]> [Security Management]> [System Security Settings]> [System-wide Security Parameters] .

Setting the required complexity of passwords is done by specifying a pattern for passwords in the Password Pattern parameter. The default setting is to set the maximum
Password Security = 8.32ANP, which means the use of passwords of a minimum length of 8 characters, a maximum size of 32 characters, the use of alphanumeric characters and punctuation characters. To connect specific password validation algorithms, use the Password validation routine parameter.

A detailed description is given in [1], section "Password Strength and Password Policies".

In addition to using internal mechanisms, authentication in Caché can be delegated to the operating system, Kerberos, or LDAP servers.

Recently, for one project, I had to check whether the Caché DBMS complies with the requirements of the new edition of the data security standard of the PCI DSS 3.2 payment card industry adopted in April 2016.

Compliance of Caché DBMS security settings with PCI DSS version 3.2 [5]


Controlled
parameter
password
politicians
PCI DSS RequirementsSettings in the System Management Portal
User authentication before changing password.
8.3
Examine the password and authentication procedures and make sure that the user is authenticated before changing the password by phone, email, using a web application or in any other remote way.
[System]> [Security Management]> [Users]> [Edit User] >

Two-factor Authentication

For authentication, you can enable two-factor authentication with the use of SMS on a mobile phone.
Set a unique initial password for each user and immediately change it when the user first logs on.
8.2.6
Setting a unique initial password for each user and changing it immediately when the user first logs on.
[System]> [Security Management]> [Users]> [Edit User] >

Change password on next login
Maximum password expiration date
8.2.4
90 days
[System Security]> [System-wide Security Parameters]

Password Expiration Days = 80
Minimum length
password
8.2.3
7 characters
[System Security]> [System-wide Security Parameters]

Password Pattern = 7.32ANP
Password complexity
8.2.3
numbers and symbols
[System Security]> [System-wide Security Parameters]

Password Pattern = 7.32ANP
Ban re
use old passwords
8.2.5
4 old passwords
No default. To implement, you can connect external authentication, or extend the functionality by implementing the application code
[System]> [Security Management]> [Users]> [Edit User]>
Password validation routine
Limit the number of invalid
attempts to enter, after which exceeded, the account is locked
8.1.6
6 tries

8.1.7
duration
lock not
less than 30 minutes
[System Security]> [System-wide Security Parameters]
Invalid login limit = 6

Disable account if login limit
reached = checked

1.6 Configuring the disconnection from the base of an inactive connection


The connection disconnection settings for inactive user sessions depend on the type of connection to Caché.

For SQL and object access via TCP, the parameter is set in the section of the management portal [Home]> [Configuration]> [SQL Settings]> [General SQL Settings] , parameter TCP Keep Alive interval (in seconds): set to 900, which will correspond to 15 minutes

For web access, the parameter is set in the “No Activity Timeout” setting for [Home]> [Configuration]> [CSP Gateway Management] . The default value needs to be replaced with 900 seconds, and enable the parameter “Apply timeout to all connections”

2 Event Logging


2.1 General Settings


To enable logging of audit events, you must enable this option for the entire Caché DBMS instance. To do this, go to the System Management Portal on the Audit management page [Home]> [Security Management]> [Auditing] , and make sure that the “Disable Auditing” option is available and the “Enable Auditing” option is not available, which means that the audit included. The opposite means that auditing is turned off.

If auditing is turned off, then it must be enabled by selecting the “Enable Auditing” menu item. Viewing the event log is possible through the System Management Portal: [Home]> [Security Management]> [Auditing]> [View Audit Database]


There are also system classes (utilities) for viewing the event log. The following data is recorded in the event log:


Access to audit data is controlled by the% DB_CACHEAUDIT resource. To disable public access to this resource, you need to make sure that the properties of this resource for Public both read (Read) and write (Write) are closed. Access to the list of resources is possible in the system management portal [Home]> [Security Management]> [Resources]> [Edit Resource], then for the selected resource, click the Edit link.

By default, the% DB_CACHEAUDIT role of the same name is assigned to the% DB_CACHEAUDIT resource. In order to restrict user access to the event logs, it is necessary to establish a list of users who belong to this role, which can be done by the system management portal:

[Home]> [Security Management]> [Roles]> [Edit Role] and then you need to select the Edit button for the role% DB_CACHEAUDIT

2.2 List of logged event types


2.2.1 Logging of access to tables containing payment card data (PCI DSS 10.2.1)


Access to tables (data sets) containing payment card data is logged using the following mechanisms:

1. A system audit mechanism that records events of the “ResourceChange” type to the event log when access rights to a resource responsible for storing payment card data are changed (access to the audit log for viewing is called via the [Home]> [Security Management] system management portal > [Auditing]> [View Audit Database] );

2. At the application level, to log access to a particular record, you can register an application event in the system and call it from the application program each time the corresponding event occurs.

[System]> [Security Management]> [Configure User Audit Events]> [Edit Audit Event]

2.2.2 Logging Attempts to Use Administrative Privileges (PCI DSS 10.2.2)


In the Caché DBMS, the actions of all users are logged, the setting of the logging method is done by specifying the events to be logged
[Home]> [Security Management]> [Auditing]> [Configure SystemEvents] All system events should be logged.

2.2.3 Logging Event Log Changes (PCI DSS 10.2.3)


The Caché DBMS uses a single audit log, which cannot be changed, except for a natural change of its content and the facts of an error, clearing the log, changing the list of audited events, etc., which cause the corresponding AuditChange event entry in the log.

The task of logging the AuditChange event is performed by including the audit of all events (see 2.2.2).

2.2.4 Logging unsuccessful attempts to obtain logical access (PCIDSS 10.2.4)


The task of logging an unsuccessful attempt to obtain logical access was performed by including the audit of all events (see 2.2.2).

Upon attempting to obtain logical access, the LoginFailure event is generated in the audit log.

2.2.5 Logging of attempts to gain access to the system (PCI DSS 10.2.5)


The task of logging an attempt to gain access to the system is performed by including the audit of all events (see 2.2.2).

Upon the unsuccessful attempt to gain access in the audit log, a LoginFailure event is generated. Upon a successful attempt to gain access in the audit log, a “Login” event is generated.

2.2.6 Logging of changes in audit parameters (PCI DSS 10.2.6)


The task of recording changes in the audit parameters is performed by including the audit of all events (see 2.2.2).

Upon attempting to obtain logical access, an “AuditChange” event is generated in the audit log.

2.2.7 Logging the creation and deletion of system objects (PCI DSS 10.2.7)


Caché DBMS records the creation, modification and deletion of the following system objects: roles, privileges, resources, users.

The task of recording the creation and deletion of system objects is performed by including the audit of all events (see 2.2.2).

Upon the creation, modification, and deletion of system objects in the audit log, events “ResourceChange”, “RoleChange”, “ServiceChange”, and “UserChange” are created.

2.3 Protecting Event Logs


You must ensure that access to the% DB_CACHEAUDIT resource is limited. That is, only administrators or persons whose duties include monitoring of logs have the right to read and write to this resource.

Following the above recommendations, I set the Caché DBMS as safe as possible. To demonstrate the fulfillment of the PCI DSS requirement of clause 8.2.5 “Prohibiting the reuse of old passwords”, I implemented a small program that will be launched by the system when a user edits his password and checks for repeated use.

To install this program, you need to import the source code using Caché Studio, Atelier or the classes import page via the control panel.
ROUTINE PASSWORD PASSWORD ;    #include %occInclude CHECK(Username,Password) PUBLIC { if '$match(Password,"(?=.*[0-9])(?=.*[a-zA-Z]).{7,}") quit $$$ERROR($$$GeneralError,"    PCI_DSS_v3.2") set Remember=4 ;  ,     PCI-DSS set GlobRef="^PASSWORDLIST" ;    set PasswordHash=$System.Encryption.SHA1Hash(Password) if $d(@GlobRef@(Username,"hash",PasswordHash)){ quit $$$ERROR($$$GeneralError,"   ") } set hor="" for i=1:1 { ;         set hor=$order(@GlobRef@(Username,"datetime",hor),-1) quit:hor="" ;     if i>(Remember-1) { set hash=$g(@GlobRef@(Username,"datetime",hor)) kill @GlobRef@(Username,"datetime",hor) kill:hash'="" @GlobRef@(Username,"hash",hash) } } ;   set @GlobRef@(Username,"hash",PasswordHash)=$h set @GlobRef@(Username,"datetime",$h)=PasswordHash quit $$$OK } 


And in the management portal, save the name of this program.


It turned out that my product configuration differed from the test one not only in the level of security, but in the number of users. There were several thousand of them in combat use. And when administering the system, I was faced with the impossibility of creating a new user by copying settings from an existing one.


DBMS developers have limited the list of output to 1000 items. My standard was lost somewhere below and the list did not fall. After consulting with the Intersystems WRC support service, I received recommendations that this problem can be solved by creating a special global node in the system area:

 %SYS>set ^CacheTemp.MgtPortalSettings($Username,"MaxUsers")=5000 

This is how it was possible to increase the number of displayed users in the drop-down list. Exploring this global, I found in it a lot of other useful saved settings of the current user. But there is an inconvenience: this global has mapping into a temporary CacheTemp database and will be deleted after reboot. And this problem can be solved only by saving this global before shutting down the system and restoring at startup.

For this, I created two programs ^% ZSART and ^% ZSTOP with the required functionality.

The source code of the program% ZSTOP
 %ZSTOP ;  quit SYSTEM ;      merge ^tmpMgtPortalSettings=^CacheTemp.MgtPortalSettings quit 


The source code of the program% ZSTART
 %ZSTART ;  quit SYSTEM ;      if $data(^tmpMgtPortalSettings) merge ^CacheTemp.MgtPortalSettings=^tmpMgtPortalSettings quit 


And going back to security and the requirements of the standard, we cannot but say something about the backup procedure. The PCI DSS standard imposes backup requirements not only on data, but also on event logs. In Caché DBMS, all logged events are recorded in the CACHEAUDIT database, which, along with other databases, can be included in the list of databases for backup.

In the delivery of the Caché DBMS, there are already several ready-made tasks for backup,
but they didn't always suit me. Each time, in a particular project, features were needed that were not in the tasks out of the box. For one project, it was necessary to automate the control over the number of copies with the possibility of deleting old ones. For another, it was necessary to estimate the estimated occupied volume of the future file before copying. And I implemented my backup program.

CustomListBackup.cls
 Include %occKeyword ///     Class %SYS.Task.CustomListBackup Extends %SYS.Task.Definition [ LegacyInstanceContext ] { ///  ..AllDatabases=1,        ..PrefixIncludeDB  ..IncludeDatabases -  Property AllDatabases As %Integer [ InitialExpression = 0 ]; ///  ..AllDatabases=1,        ,   ..IgnoreForAllDatabases   Property IgnoreForAllDatabases As %String(MAXLEN = 32000) [ InitialExpression = " AllDatabases=0   " ]; ///  ..IgnoreTempDatabases=1,     Property IgnoreTempDatabases As %Integer [ InitialExpression = 1 ]; ///  ..IgnorePreparedDatabases=1,     Property IgnorePreparedDatabases As %Integer [ InitialExpression = 1 ]; ///  ..AllDatabases=0   PrefixIncludeDB  ,     ,     ..PrefixIncludeDB Property PrefixIncludeDB As %String [ SqlComputeCode = {S {*}=..ListNS()}, SqlComputed ]; ///  ..AllDatabases=0,         ..IncludeDatabases ,   Property IncludeDatabases As %String(MAXLEN = 32000) [ InitialExpression = {" AllDatabases=1   "_..ListDB()} ]; ///      Parameter TaskName = "CustomListBackup"; /// ,    Property DirBackup As %String(MAXLEN = 1024) [ InitialExpression = {##class(%File).NormalizeDirectory("Backup")} ]; /// ,    Property DirBackupLog As %String(MAXLEN = 1024) [ InitialExpression = {##class(%File).NormalizeDirectory("Backup")} ]; ///   (Full-, Incremental-, Cumulative-) Property TypeBackup As %String(DISPLAYLIST = ",Full,Incremental,Cumulative", VALUELIST = ",Full,Inc,Cum") [ InitialExpression = "Full", SqlColumnNumber = 4 ]; ///     Property PrefixBackUpFile As %String [ InitialExpression = "back" ]; ///    ,    Property MaxBackUpFiles As %Integer [ InitialExpression = 3 ]; ClassMethod DeviceIsValid(Directory As %String) As %Status { If '##class(%Library.File).DirectoryExists(Directory) quit $$$ERROR($$$GeneralError,"Directory does not exist") quit $$$OK } ClassMethod CheckBackup(Device, MaxBackUpFiles, del = 0) As %Status { set path=##class(%File).NormalizeFilename(Device) quit:'##class(%File).DirectoryExists(path) $$$ERROR($$$GeneralError," "_path_"  ") set max=MaxBackUpFiles set result=##class(%ResultSet).%New("%File:FileSet") set st=result.Execute(path,"*.cbk",,1) while result.Next() { If result.GetData(2)="F" { continue:result.GetData(3)=0 set ts=$tr(result.GetData(4),"-: ") set ts(ts)=$lb(result.GetData(1),result.GetData(3)) } } #;        set i="" for count=1:1 { set i=$order(ts(i),-1) quit:i="" #;           if $data(size),'$data(delta) set delta=size-$lg(ts(i),2) #;         if '$data(size) set size=$lg(ts(i),2) #;        ,        if count'<max { set cbk=$lg(ts(i),1) set log=$replace($lg(ts(i),1),".cbk",".log") if del { if ##CLASS(%File).Delete(cbk) if ##CLASS(%File).Delete(log) } } } do result.%Close() If $$$isUNIX quit $$$OK ##;     #;        set drive=$e(path,1) do ##CLASS(%File).GetDirectorySpace(drive_":/",.free,.total,0) #;            quit:($g(size)+$g(delta))>$g(free) $$$ERROR($$$GeneralError,"     ,     :("_$g(size)_"+"_$g(delta)_")>"_$g(free)) quit $$$OK } Method OnTask() As %Status { do $zu(5,"%SYS") set list="" merge oldDBList=^SYS("BACKUPDB") kill ^SYS("BACKUPDB") #;        set status=$$$OK try { ##;     ,       ##;            set status=..CheckBackup(..DirBackup,..MaxBackUpFiles,1) quit:$$$ISERR(status) #;    if ..AllDatabases { set vals="" set disp="" set rss=##class(%ResultSet).%New("Config.Databases:List") do rss.Execute() while rss.Next(.sc) { if ..IgnoreForAllDatabases'="",(","_..IgnoreForAllDatabases_",")[(","_$zconvert(rss.Data("Name"),"U")_",") continue if ..IgnoreTempDatabases continue:..IsTempDB(rss.Data("Name")) if ..IgnorePreparedDatabases continue:..IsPreparedDB(rss.Data("Name")) set ^SYS("BACKUPDB",rss.Data("Name"))="" } } else { #;   PrefixIncludeDB  ,          ..PrefixIncludeDB if ..PrefixIncludeDB'="" { set rss=##class(%ResultSet).%New("Config.Databases:List") do rss.Execute(..PrefixIncludeDB_"*") while rss.Next(.sc) { if ..IgnoreTempDatabases continue:..IsTempDB(rss.Data("Name")) set ^SYS("BACKUPDB",rss.Data("Name"))="" } } #;      if ..IncludeDatabases'="" { set rss=##class(%ResultSet).%New("Config.Databases:List") do rss.Execute("*") while rss.Next(.sc) { if ..IgnoreTempDatabases continue:..IsTempDB(rss.Data("Name")) if (","_..IncludeDatabases_",")'[(","_$zconvert(rss.Data("Name"),"U")_",") continue set ^SYS("BACKUPDB",rss.Data("Name"))="" } } } do ..GetFileName(.backFile,.logFile) set typeB=$zconvert($e(..TypeBackup,1),"U") set:"FIC"'[typeB typeB="F" set res=$$BACKUP^DBACK("",typeB,"",backFile,"Y",logFile,"NOINPUT","Y","Y","","","") if 'res set status=$$$ERROR($$$GeneralError,": "_res) } catch { set status=$$$ERROR($$$GeneralError,": "_$ze) set $ze="" } kill ^SYS("BACKUPDB") merge ^SYS("BACKUPDB")=oldDBList quit status } ///    Method GetFileName(aBackupFile, ByRef aLogFile) As %Status { set tmpName=..PrefixBackUpFile_"_"_..TypeBackup_"_"_$s(..AllDatabases:"All",1:"List")_"_"_$zd($h,8)_$tr($j($i(cnt),3)," ",0) do { s aBackupFile=##class(%File).NormalizeFilename(..DirBackup_"/"_tmpName_".cbk") } while ##class(%File).Exists(aBackupFile) set aLogFile=##class(%File).NormalizeFilename(..DirBackupLog_"/"_tmpName_".log") quit 1 } ///      ClassMethod IsPreparedDB(name) { if (",ENSDEMO,ENSEMBLE,ENSEMBLEENSTEMP,ENSEMBLESECONDARY,ENSLIB,CACHESYS,CACHELIB,CACHETEMP,CACHE,CACHEAUDIT,DOCBOOK,USER,SAMPLES,")[(","_$zconvert(name,"U")_",") quit 1 quit 0 } ///      ClassMethod IsTempDB(name) { quit:$zconvert(name,"U")["TEMP" 1 quit:$zconvert(name,"U")["SECONDARY" 1 quit 0 } ///        ClassMethod ListDB() { set list="" set rss=##class(%ResultSet).%New("Config.Databases:List") do rss.Execute() while rss.Next(.sc) { set list=list_","_rss.Data("Name") } quit list } ClassMethod ListNS() [ Private ] { set disp="" set tRS = ##class(%ResultSet).%New("Config.Namespaces:List") set tSC = tRS.Execute() While tRS.Next() { set disp=disp_","_tRS.GetData(1) } set %class=..%ClassName(1) $$$comSubMemberSet(%class,$$$cCLASSproperty,"PrefixIncludeDB",$$$cPROPparameter,"VALUELIST",disp) quit "" } ClassMethod oncompile() [ CodeMode = generator ] { $$$defMemberKeySet(%class,$$$cCLASSproperty,"PrefixIncludeDB",$$$cPROPtype,"%String") set updateClass=##class("%Dictionary.ClassDefinition").%OpenId(%class) set updateClass.Modified=0 do updateClass.%Save() do updateClass.%Close() } } 


All the basic wishes in it are implemented: both limiting the number of copies with deleting old ones and forecasting the size of a new file, and different ways to select or exclude from the list of databases. We import into the system and create a new task using the Task Manager.



And we include the database in the list of replicated databases.

All examples are given for the Caché 2016.1 version of the DBMS. Examples are given solely for informational purposes and installing them into the product system should be done only after serious testing. I would be glad if the above code helped someone in the work, or saved from errors.

Repository on Github

In the preparation of this article used materials:

  1. Caché Security Administration Guide (InterSystems)
  2. Caché Installation Guide. Preparing for Caché Security (InterSystems)
  3. Caché System Administration Guide (InterSystems)
  4. Introduction to Caché. Caché Security (InterSystems)
  5. PCI DSS.RU. Security audit requirements and procedure. Version 3.2

Source: https://habr.com/ru/post/342476/


All Articles