Content Services security comprises a combination of authentication and authorization.
Authentication is about validating that a user or principal is who or what they claim to be. A user’s credentials can take many forms and can be validated in a number ways. For example, a password validated against an LDAP directory, or a Kerberos ticket validated against a Microsoft Active Directory Server.
Content Services includes:
- An internal, password-based, authentication implementation
- Support to integrate with many external authentication environments
- The option to write your own authentication integration and to use several of these options simultaneously
Content Services can integrate with LDAP, Microsoft Active Directory Server, the Java Authentication and Authorization Service (JAAS) and Kerberos. A user ID can also be presented as an HTML attribute over HTTPS to integrate with web-based single-sign-on solutions.
Authorization determines what operations an authenticated user is allowed to perform. There are many authorization models. Popular ones include: Role Based Access Control (RBAC), UNIX-style Access Control Lists (ACLs) and extended ACLs, Windows-style ACLs, and many more. Authorization requirements for the management of records are more detailed and include additional requirements, for example, enforcing access based on security clearance or record state.
Authorization is based on UNIX-extended ACLs. Each node in the repository has an ACL that is used to assign permissions to users and groups. Operations, such as creating a new node, describe what permissions are required to carry out the operation. ACLs are then used to determine if a given user can execute the operation based on the permissions that have been assigned directly to the user or indirectly through a group. An operation is invoking a method on a public service bean. For example, creating a user’s home folder requires invoking methods on several public services; to create the folder, set permissions, disable permission inheritance, and so on. Each public service method invocation will check that the user is allowed to execute the method.
By convention, public service beans are the beans whose names start with capital letters, such as the NodeService
. You configure the security requirements for public service beans in XML. A given method on a particular service might be available to all users, all users in a specified group, all users with a specified role, or users who have particular permissions on specified arguments to the method or its return value. In addition, for methods that return collections or arrays, their content can be filtered based on user permissions. If the authorization requirements for a method call are not met, the method call will fail and it’ll throw an AccessDeniedException
. Non-public beans, such as nodeService
, do not enforce security; use these only when the enforcement of authorization is not required.
Permission assignments are made in Access Control Lists (ACLs), which are lists of Access Control Entries (ACEs). An ACE associates an authority (group or user) with a permission or set of permissions, and defines whether the permission is denied or allowed for the authority. Every node has a related ACL. When you create a node, it automatically inherits an ACL from its parent. You can alter this behavior after node creation by breaking inheritance or modifying the ACL.
The XML configuration for permissions also defines a context-free ACL for ACEs that apply to all nodes. For example, you could use this to assign everyone Read access to all nodes regardless of what individual ACLs any node has set. (See the Permissions section in this chapter for more details on how to modify the permission model.)
<!-- Extension to alfresco\model\permissionDefinitions.xml -->
<globalPermission permission="Read" authority="GROUP_EVERYONE" />
A check that a user has Read permission for a node is done in two stages. First, the context-free ACL is checked to see if it allows access. If not, the ACL assigned or inherited by the node is checked. A user might be allowed to perform an operation because of permissions assigned to the context-free ACL, assigned to the node’s ACL, inherited by the node from its parent, or a combination of all three.
Mitigate brute force attack on user passwords
Content Services provides basic out-of-the-box protection against brute force attacks on password logins.
To mitigate brute force attacks on user passwords, after a few failed login attempts for any given user id, the user id is locked out and marked as protected
. The user id stays in the protected
mode for a six seconds protection period. During this time, even if the correct login details are specified, the user can’t login. After the six seconds protection period is over, the user can login with the correct login details.
To summarize, once a user id is protected
:
- the schedule causes a six seconds delay between the allowed login attempts.
- authentication requests occurring more frequently than the permitted schedule are denied.
- next login attempt that is denied due to the rate limiting algorithm generates a WARN message in the Alfresco log file (only once).
- for every consecutive failed login attempt, if the limit exceeds 10 attempts, a WARN message is shown in the Alfresco log file (only once).
- details about authentication attempts, including the number of login attempts and time stamp of last login attempt, are cached.
The administrator receives one log message per protection period. To avoid username disclosure in logs, the message displays only the first two letters of the username.
The user id stays as protected
until a correct authentication request is processed after the six seconds protection period. The entry is then removed from the cache.
This login protection feature is enabled by default, and can be configured by adding the following properties to the alfresco-global.properties
file.
Property | Description |
---|---|
authentication.protection.enabled | Specifies if the login protection feature is enabled or disabled, for example true . |
authentication.protection.limit | Specifies the number of attempts after which the user id becomes protected, for example 10 . |
authentication.protection.periodSeconds | Specifies the protection period after which a valid login attempt can be done, for example 6 . |
Note: This feature provides some basic protections against brute force attacks by slowing down repeated logins, but it does not replace more advanced brute force attack detection and mitigation that would be done on the network level or through log analysis.
Manage Alfresco keystores
This section brings you up-to-speed on Alfresco keystores and truststores.
What is a Keystore and a Truststore?
In Alfresco both the keystore and truststore file types are Java Keystores stored in one of the formats JKS, JCEKS, or PKCS12. We use a keystore and a truststore when Alfresco needs to communicate over SSL/TLS.
Usually, these are password-protected files that sit on the same file system as a running Alfresco instance. The default format used for these files is JKS until Java 8.
Since Java 9, though, the default keystore format is PKCS12. The biggest difference between JKS and PKCS12 is that JKS is a format specific to Java, while PKCS12 is a standardized and language-neutral way of storing encrypted private keys and certificates.
Java KeyStore
A Java keystore stores private key entries, certificates with public keys or just secret keys that we may use for various cryptographic purposes. It stores each by an alias for ease of lookup.
Generally speaking, keystores hold keys that Alfresco owns, or you as a customer owns, that we can use to prove the integrity of a message and the authenticity of the sender, say by signing payloads.
Usually, we’ll use a keystore when we are a server and want to use HTTPS, such as the Repository (i.e. alfresco.war
).
During an SSL handshake, the server looks up the private key from the keystore and presents its corresponding public key
and certificate to the client.
Correspondingly, if the client also needs to authenticate itself (a situation called mutual authentication), such as with Solr, then the client also has a keystore and also presents its public key and certificate.
There’s no default keystore, so if we want to use an encrypted channel, we’ll have to set javax.net.ssl.keyStore
and
javax.net.ssl.keyStorePassword
. If our keystore format is different than the default, we could use javax.net.ssl.keyStoreType
to customize it.
Of course, we can use these keys to service other needs as well. Private keys can sign or decrypt data, and public keys can verify or encrypt data (i.e. node property/metadata encryption). Secret keys can perform these functions as well. A keystore is a place that we can hold onto these keys.
We can also interact with the keystore programmatically.
Java TrustStore
A truststore is the opposite, while a keystore typically holds onto certificates that identify us (i.e the Alfresco Repository server), a truststore holds onto certificates that identify others (such as the Alfresco Solr client).
In Java, we use it to trust the third party we’re about to communicate with (i.e. Solr).
If the Solr client talks to the Repository server over HTTPS, the Repository server will look up the associated key from its keystore and present the public key and certificate to the Solr client.
We, the Solr client, then look up the associated certificate in our truststore. If the certificate or
Certificate Authorities (CA) presented by the external server is not in our truststore, we’ll get an SSLHandshakeException
and the connection won’t be set up successfully.
Java has bundled a truststore called cacerts
and it resides in the $JAVA_HOME/lib/security
directory.
It contains default, trusted Certificate Authorities (CA):
$ keytool -list -keystore $JAVA_HOME/lib/security/cacerts
Warning: use -cacerts option to access cacerts keystore
Enter keystore password:
***************** WARNING WARNING WARNING *****************
* The integrity of the information stored in your keystore *
* has NOT been verified! In order to verify its integrity, *
* you must provide your keystore password. *
***************** WARNING WARNING WARNING *****************
Keystore type: JKS
Keystore provider: SUN
Your keystore contains 93 entries
verisignclass2g2ca [jdk], 13 Jun 2018, trustedCertEntry,
Certificate fingerprint (SHA-256): 3A:43:E2:20:FE:7F:3E:A9:65:3D:1E:21:74:2E:AC:2B:75:C2:0F:D8:98:03:05:BC:50:2C:AF:8C:2D:9B:41:A1
...
Here, we can override the default truststore location via the javax.net.ssl.trustStore
property. Similarly, we can set
javax.net.ssl.trustStorePassword
and javax.net.ssl.trustStoreType
to specify the truststore’s password and type.
Creating Java Keystores and Certificates
This is done from command line with the keytool
, which provides the ability to create keystores and truststores of the
different types, including private and public certificates.
Introduction to Alfresco keystores an truststores
When there is secure communication (i.e. HTTPS) between different Alfresco services, the following relationships must be satisfied:
- The Repository is a client of Solr:
- A Repository key must be generated and must be included in the Repository keystore (
ssl.keystore
) - A Repository public certificate must be included in the Solr truststore (
ssl.repo.client.truststore
)
- A Repository key must be generated and must be included in the Repository keystore (
- Solr is a client of the Repository and Solr:
- A Solr key must be generated and must be included in the Solr keystore (
ssl.repo.client.truststore
) - A Solr public certificate must be included in the Repository truststore (
ssl.truststore
) and Solr truststore (ssl.repo.client.truststore
)
- A Solr key must be generated and must be included in the Solr keystore (
- Zeppelin is client of Repository (Zeppelin is a product only available for Insight Engine Enterprise):
- A Zeppelin key must be generated and must be included in the Zeppelin keystore (
ssl.repo.client.keystore
) - A Zeppelin public certificate must be included in the Repository truststore (
ssl.truststore
) - Note. the same key certificates is used for both Solr and Zeppelin, as both are clients of the Repository
- A Zeppelin key must be generated and must be included in the Zeppelin keystore (
- When accessing Solr from a browser, the browser is client of Solr:
- A Browser key must be installed in the web browser in order to access Solr Web Console
The following picture illustrates:
Additionally, to support Alfresco encryption feature, a metadata cyphering key is generated and included on a keystore to be used by the Repository when encrypting node properties.
These keystore and truststore files can be generated manually but it’s easier to use the https://github.com/Alfresco/alfresco-ssl-generator GitHub project. Follow the Search Services security documentation for information on how to set this up on Windows or Linux.
Alfresco default keystore and backup keystore
The out-of-the-box Content Services installation has a pre-configured main keystore, which contains a secret key generated by Content Services. If you want to use encrypted properties, you should create your own keystore with your own password, and update the metadata file appropriately.
The default keystore configuration protects the keys by using two levels of passwords - a keystore password and a password
for each key. Currently, the keystore contains only a metadata secret key that is used for encrypting and decrypting node
properties that are of type d:encrypted
.
You can also configure a backup keystore. This is useful in case the keys need to be changed. The user can back up the main keystore to the backup keystore location and create a new keystore in its place.
If both the main and backup keystores are configured, the repository encryption works in the fallback mode. In this mode, the node properties are decrypted with the main keystore’s metadata key first. If that fails, the backup keystore’s metadata key is tried. This allows the keystores to be changed on the disk and reloaded without affecting the running of the repository.
Keystores are also used to protect the communication between the Repository and Solr using encryption and mutual authentication. The keystores store RSA keys and certificates in this case. For more information on how to turn on HTTPS between the Repository and Solr, and how to re-generate the default certificate, see Solr security.
Alfresco Keystore configuration
The way you configure keystores in Content Services has changed. Previously the configuration was stored in properties
files like keystore-passwords.properties
with passwords in plain text. The following properties that were used to
configure the keystores have been deprecated.
encryption.keystore.keyMetaData.location
encryption.ssl.keystore.keyMetaData.location
encryption.ssl.truststore.keyMetaData.location
encryption.keystore.backup.keyMetaData.location
The new way of specifying the configuration is to use JVM system properties:
JAVA_TOOL_OPTIONS: "
-Dencryption.keystore.type=JCEKS
-Dencryption.cipherAlgorithm=DESede/CBC/PKCS5Padding
-Dencryption.keyAlgorithm=DESede
-Dencryption.keystore.location=/usr/local/tomcat/shared/classes/alfresco/extension/keystore/keystore
-Dmetadata-keystore.password=mp6yc0UD9e
-Dmetadata-keystore.aliases=metadata
-Dmetadata-keystore.metadata.password=oKIWzVdEdA
-Dmetadata-keystore.metadata.algorithm=DESede
"
Note: The old way of configuring keystores will still work for backwards compatibility but it’s not recommended for security reasons. If the old approach is used you’ll see a warning in the logs.
You can configure the main and backup keystores using the alfresco-global.properties
file.
To configure the main keystore, set the following properties in the alfresco-global.properties
file:
Note: The “metadata-keystore” properties need to be specified in the
JAVA_TOOL_OPTIONS
property in<TOMCAT_HOME>/bin/catalina.sh
for Linux based users and<TOMCAT_HOME>/bin/catalina.bat
for Microsoft Windows users. The old keystore file can be found in the distribution zipkeystore/metadata-keystore
.
Main keystore and backup:
Property | Description |
---|---|
encryption.keystore.location | Specifies the location of the main keystore.encryption.keystore.location=${dir.keystore}/keystore |
encryption.keystore.provider | Specifies the main keystore provider. |
encryption.keystore.type | Specifies the main keystore type.encryption.keystore.type=JCEKS |
encryption.keystore.backup.location | Specifies the location of the backup keystore.encryption.keystore.backup.location=${dir.keystore}/backup-keystore |
encryption.keystore.backup.provider | Specifies the backup keystore provider. |
encryption.keystore.backup.type | Specifies the backup keystore type. encryption.keystore.backup.type=JCEKS |
Keys for secure communication (HTTPS) between Repository and Solr:
Property | Description |
---|---|
ssl-keystore.password | The keystore password. |
ssl-keystore.aliases=ssl-alfresco-ca,ssl-repo | Key data bytes in base64. |
ssl-keystore.ssl-alfresco-ca.password | Key password. |
ssl-keystore.ssl-repo.password | Key password. |
ssl-truststore.password | The keystore password |
ssl-truststore.aliases=alfresco-ca,ssl-repo-client | A comma separated list of aliases for the keys in the keystore. |
ssl-truststore.alfresco-ca.password= | Key password. |
ssl-truststore.ssl-repo-client.password= | Key password. |
Keys for node property (metadata) encryption keystore:
Property | Description |
---|---|
metadata-keystore.password | The keystore password |
metadata-keystore.aliases=metadata | A comma separated list of aliases for the keys in the keystore. |
metadata-keystore.metadata.password | Key password. |
metadata-keystore.metadata.algorithm | Key algorithm. |
The new keystore properties use the following format:
[keystore-id].password - keystore password
[keystore-id].aliases - comma separated list of aliases for the keys in the keystore
[keystore-id].[alias].keyData - key data bytes in base64
[keystore-id].[alias].algorithm - key algorithm
[keystore-id].[alias].password - key password
The keystore-id can be one of the predefined IDs: metadata-keystore
, metadata-backup-keystore
, ssl-keystore
, ssl-truststore
.
Note: The default configuration of Content Services contained a “metadata” keystore, but it was insecure if you did not regenerate it with your own password. This default keystore has been removed. To ensure your installation is secure you need to regenerate the keystore and configure the new one with the properties above.
The default algorithm used to generate keys does not have acceptable strength (DES). We recommend you use AES which has been reflected in the default configuration. The recommended property values for this will be:
encryption.keystore.type=pkcs12
encryption.cipherAlgorithm=AES/CBC/PKCS5Padding
encryption.keyAlgorithm=AES
metadata-keystore.password=<password>
metadata-keystore.aliases=metadata
metadata-keystore.metadata.password=<password>
metadata-keystore.metadata.algorithm=AES
And the keys can be generated using
keytool -genseckey -dname "$CERT_DNAME" -validity ${CERT_VALIDITY} -alias metadata -keyalg AES -keysize 256 -keystore ${TOMCAT_DIR}/shared/classes/alfresco/keystore/keystore -storetype pkcs12 -storepass ${KEYSTORE_PASSWORD}
Important: Currently it’s not possible to upgrade the key in keystore to a new one which means during the upgrade process you’ll need to specify your configuration in a more secure way and use the old keystore file. The configuration for this upgrade scenario will be:
encryption.keystore.type=JCEKS encryption.cipherAlgorithm=DESede/CBC/PKCS5Padding encryption.keyAlgorithm=DESede encryption.keystore.location=<path-to-keystore-file> metadata-keystore.password=mp6yc0UD9e metadata-keystore.aliases=metadata metadata-keystore.metadata.password=oKIWzVdEdA metadata-keystore.metadata.algorithm=DESede
Some other general encryption properties are:
encryption.keySpec.class=org.alfresco.encryption.DESEDEKeyGenerator
encryption.keyAlgorithm=DESede
encryption.cipherAlgorithm=DESede/CBC/PKCS5Padding
Because of these encryption properties, the keystores and metadata files can be easily located. Also, the metadata file uses a clear text password to access the keystore. For this reason, appropriate operating system permissions should be applied so that the files can’t be accidentally changed nor read by anyone other than an administrator and the username running the repository.
Each keystore must have a corresponding keystore metadata file. This file contains the passwords, its keys, and other metadata relevant to the keystore. The metadata file must contain three entries:
aliases=<active key aliases in the key store>
keystore.password=<key store password>
metadata.password=<metadata key password>
At bootstrap, the repository checks if the metadata key in the main keystore has been changed (unless running in the fallback mode, in which case the backup keystore is checked instead). This prevents accidental changes to the keystore. If it detects that the metadata key has been changed, an exception will occur and the bootstrap will stop.
Keystore generation
Keystore generation can be automatic or manual.
Automatic keystore generation
During bootstrap, if the repository detects a missing secret key keystore, it’ll dynamically create a keystore containing a single metadata secret key. In order to do this, the repository assumes the existence of a keystore metadata file containing information about the metadata key. Specifically, it expects the following properties to be set:
Property | Description |
---|---|
[keystore-id].password | The keystore password. |
[keystore-id].aliases | A comma separated list of aliases for the keys in the keystore. |
[keystore-id].[alias].keyData | Key data bytes in base64. |
[keystore-id].[alias].algorithm | Specifies the key algorithm used to generate the secret key.Each Java environment may support a different set of algorithms. For the list of algorithm names that can be specified, see SecretKeyFactory Algorithms. For keytool defaults specific to the secret key generation, see the Oracle documentation - keytool. |
[keystore-id].[alias].password | Key password. |
The keyData
can be generated by executing the class org.alfresco.encryption.GenerateSecretKey
as shown below:
java -classpath "projects/3rd-party/lib/commons/commons-codec-1.4.jar:projects/core/build/dist/alfresco-core-4.0.a.jar"
org.alfresco.encryption.GenerateSecretKey
Manual keystore generation
A new keystore can be generated using the Java keytool
command as shown below:
keytool -genseckey -alias metadata -keypass <metadata key password> -storepass <key store password> -keystore keystore
-storetype JCEKS -keyalg DESede
Note: Make sure the keystore is placed in the location specified by the property
encryption.keystore.location
and that the passwords you’ve used in thekeytool
commands are placed in the file specified by the propertyencryption.keystore.keyMetaData.location
.
Keystore key registration
The keystore keys are registered with the repository to ensure that they’re not accidentally changed.
During bootstrap and JMX keystore reload and re-encryption operations, the repository checks if the main keystore’s keys and the metadata key have changed. If they have changed, the repository throws an exception.
secure credentials store
Alfresco uses a secure store for credentials it needs to maintain for user accounts. This data encryption uses secret keys which are stored in the Java keystore. It is possible to rotate the keys used for credentials encryption.
During bootstrap, the repository checks whether the keys in the main encrypted properties keystore have been changed in order to detect any accidental keystore changes.
However if you purposely want to change your keys, you can do so and the repository will re-encrypt any existing encrypted node properties for you. The newly encrypted node properties will be encrypted using the new keys.
Changing your keys involves backing up your keystore to a specific location and creating a new keystore in its place. This can be done in two ways:
- During bootstrap
- During runtime (not in Alfresco Community Edition)
Bootstrap Re-encryption
Re-encryption occurs during the repository bootstrap. For bootstrap re-encryption, follow the steps below:
- Stop the Alfresco Content Services server.
-
Set the following property in the
alfresco-global.properties
file.encryption.bootstrap.reencrypt=true
-
Backup the current keystore to backup-keystore as shown below:
mv keystore backup-keystore mv keystore-passwords.properties backup-keystore-passwords.properties
- Copy your new keystore over the old keystore.
- Update keystore-passwords.properties with the passwords you used to create the keystore. In other words, update the
keystore.password
property with the keystore password and themetadata.password
property with the metadata key password. - Restart the server.
Runtime Re-encryption
Re-encryption occurs while the repository is running. For runtime re-encryption, follow the steps below:
-
Backup the current keystore to backup-keystore.
mv keystore backup-keystore mv keystore-passwords.properties backup-keystore-passwords.properties
- Copy your new keystore over the old keystore.
-
In your JMX console, execute the operation Encryption > Operations > Encrypt.
This will re-read the main and backup keystores and re-encrypt the encrypted properties. The repository can continue to run during this operation; any newly-created encrypted properties will be encrypted with the new key.
Note: Only a single re-encryption can be done at a particular time. If a re-encrypt is already running then subsequent requests have no effect.
Cryptographic password hashing
Content Services uses cryptographic password hashing technique to securely store passwords.
All versions prior to Alfresco One 5.1.5 used the MD4 (Message Digest 4) and SHA256 hash algorithms (mainly to support NTLM) to store critical data. But this is no longer considered a secure approach as the hashed password is very easy to decrypt. You now have the option to configure Content Services to use Bcrypt to store passwords. By default, the system uses MD4 to allow users to use MD4 hashed passwords for alfrescoNTLM
authentication.
Bcrypt is an adaptive hash function based on the Blowfish symmetric block cipher cryptographic algorithm. It is incredibly slow to hash input compared to other functions, but this results in a much better output hash. Content Services is configured to use a strength of 10
to provide a good compromise of speed and strength.
With Bcrypt, the hashing algorithm (also called an encoder) can be configured by setting the system.preferred.password.encoding
property in the alfresco-global.properties
file. The supported values for this property are:
md4
sha256
bcrypt10
If you provide a different value, the repository won’t start.
To maintain backwards compatibility with previous versions, the default setting for this property is:
system.preferred.password.encoding=md4
After upgrading to the latest Content Services version, when the user logs in or changes the password, the system rehashes the password using the preferred encoding mechanism and stores the mechanism being used. If the preferred encoding is set to md4
, the system moves the current hashed passwords for that user.
Note: If SAML SSO is enabled, cryptographic password rehashing won’t work at login.
You can run a background job to completely remove all the old hashed passwords for those users that have not logged in yet. If the system is still set to md4
, all user objects will be upgraded. However, the background job will maintain the current hash.
If the background job is executed after the system.preferred.password.encoding
property has been changed, it’ll double-hash all the user objects in the system (unless they have already been upgraded by the user logging in). As a result, the system will temporarily hash (until the user logs in) the current hashed password, store the list of encoders used, and clean out the old hashes.
The background job uses the repository’s BatchProcessor
to execute the job. The execution of the job can be controlled if necessary via the following properties:
Property | Description |
---|---|
system.upgradePasswordHash.jobBatchSize | Specifies the number of user objects to process in each batch. |
system.upgradePasswordHash.jobQueryRange | Specifies the nodeId range to search for in each iteration. |
system.upgradePasswordHash.jobThreadCount | Specifies the number of threads the batch processor uses. |
Out of the box, this background job is enabled but set to a future date. To configure it, set the system.upgradePasswordHash.jobCronExpression
property in the alfresco-global.properties
file. For example, the following setting runs the job every 10 minutes:
system.upgradePasswordHash.jobCronExpression=0 0/10 * * * ?
Alternatively, the job can be executed immediately via a JMX console. The job makes use of JobLockService
so it is safe to run in a clustered environment.
If the password upgrade job is enabled, make sure you enable the log4j.logger.org.alfresco.repo.security.authentication.UpgradePasswordHashWorker
logging in log4j.properties
.
You can either set it to trace
or debug
as shown below:
log4j.logger.org.alfresco.repo.security.authentication.UpgradePasswordHashWorker=trace
OR
log4j.logger.org.alfresco.repo.security.authentication.UpgradePasswordHashWorker=debug
Trace
displays a list of all the processed users. Debug
is a slightly less verbose output; it displays a list of only those users whose password was changed.
To monitor users that have their passwords upgraded when they log in, add the following in log4j.properties
:
log4j.logger.org.alfresco.repo.security.authentication.HashPasswordTransactionListener=debug
Encrypting properties
The alfresco-global.properties
file (and other subsystem properties file) holds configuration properties that contain sensitive information or passwords, such as db.password
. All the properties that can be specified in Content Services under the alfresco-global.properties
file can be encrypted.
Use this information to encrypt any property using the Alfresco Encrypted Properties Management Tool. This tool uses the RSA/ECB/PKCS1PADDING encryption algorithm.
Note: This functionality is not related to cryptographic password hashing.
Important: Boolean properties, number properties, and properties that contain expressions can’t be encrypted.
The values for some of the properties that may contain sensitive data (see the list below) is hidden from JMX whereas other values, including non-sensitive values are shown in JMX. The administrator can set new values for the security-sensitive properties in JMX but they can’t see the old value.
Here is the list of protected attributes (the value for these will be masked in the JMX console and Admin Console UI):
alfresco_user_store.adminpassword
db.password
mail.password
solr.solrPassword
cryptodoc.jce.key.passwords
cryptodoc.jce.keystore.password
ldap.synchronization.java.naming.security.credentials
Encrypting configuration properties
You can encrypt sensitive properties in the alfresco-global.properties
configuration file.
-
Run the Alfresco Encrypted Properties Management Tool.
-
Navigate to
<ALFRESCO_HOME>/bin
directory. -
Locate the Alfresco Encrypted Properties Management Tool,
alfresco-spring-encryptor.jar
. -
Run the executable jar file.
java -jar alfresco-spring-encryptor.jar
-
-
Generate the public and private keys using the
initkey
function. The public and private key pair is stored in the enterprise directory.java -jar alfresco-spring-encryptor.jar initkey c:/alfresco/tomcat/shared/classes
You now have a public key (
alfrescoSpringKey.pub
) and a private key (alfrescoSpringKey.pri
) in your<ALFRESCO_HOME>/tomcat/shared/classes/alfresco/extension/enterprise
directory.Note: The private key file should be secured with the operating system permissions so that only the Content Services process can read it.
Note: Anyone can encrypt new values with the public key but only the Alfresco process can read the plain text value with the private key.
-
Generate the encrypted string for your password/value using the
encrypt
function.java -jar alfresco-spring-encryptor.jar encrypt c:/alfresco/tomcat/shared/classes <password>
Note: In the above command, remember to replace
<password>
with the actual password that you want to encrypt. -
Validate that the encrypted value obtained in Step 3 will decrypt the password.
-
Run the
validate
function.java -jar alfresco-spring-encryptor.jar validate c:/alfresco/tomcat/shared/classes <encrypted value>
Note: In the above command, remember to replace
<encrypted value>
with encrypted string value obtained in Step 3. -
You will be prompted to specify the value. Enter the password/value you want to encrypt.
-
You will be prompted to specify the value again. Enter the password/value you want to encrypt.
-
-
Add the encrypted password to
<ALFRESCO_HOME>/tomcat/shared/classes/alfresco-encrypted.properties
file.db.password.enc=ENC(<enter encrypted password here>)
For example:
db.password.enc=ENC(QcAf1Lr81meuP2p6Lu9ZQqFY1AsCfoWd)
Note: Uncomment the
db.password.enc
property by removing the “#” character. -
Set the value of the
db.password
property in thealfresco-global.properties
file to point to thedb.password.enc
property in thealfresco-encrypted.properties
file.db.password=${db.password.enc}
Note: Uncomment the
db.password
property by removing the “#” character.
Authorities
Authorities are people (or persons) or groups.
A group can contain people or other groups as members. The authorities assigned to a user at any time are the userName
from their associated Person
node, all of the groups in which the user is a direct or indirect member, and any appropriate dynamic authorities. Dynamic authorities are used for internal roles.
Dynamic authorities and roles
Content Services uses some custom roles. To implement a custom role, you create a dynamic authority for that role and assign global permissions to it. The internal roles have not been assigned any object-specific rights.
The internal roles are:
ROLE_ADMINISTRATOR
is assigned to the default administrators for the configured authentication mechanisms or members of the administration groups defined on theAuthorityServiceImpl
bean. This role has all rights.ROLE_OWNER
is assigned to the owner of a node. If there is no explicit owner, this role is assigned to the creator. This role has all rights on the owned node.ROLE_LOCK_OWNER
is assigned to the owner of the lock on a locked node. This supports a lock owner’s right to check in, cancel a check out, or unlock the node.
Alfresco Share supports the assignment of permissions only to the owner role. You can use such things as the Java API and scripting to make other assignments.
Note: Hierarchical and zoned roles can be added in the future to avoid the hidden group implementation for true roles.
People and users
When a user logs in, Content Services validates the user’s identifier and password. It uses the identifier to look up the appropriate person details for the user, using the userName
property on the Person type. You can configure this look-up to be case sensitive or case insensitive. The userName
property on the matching Person node is used as the actual user authority; it might differ in case from the user identifier presented to the authentication system. After the Person
node look-up, Content Services is case sensitive when matching authorities to permissions, group membership, roles, and for all other authorization tests.
Any user, who authenticates by any mechanism, must have an associated person node. Person
nodes can be:
- Explicitly created
- Created on demand with some default entries
- Created from LDAP synchronization
Person nodes are explicitly created when using Alfresco Share to manage users.
By default, person nodes are auto-created if not present. If an external authentication system is configured, when any user authenticates, an appropriate person node might not exist. If a person node does not exist and auto-creation is enabled, a person node will then be created using the identifier exactly as presented by the user and validated by the authentication system. The auto-created Person node’s userName will have the same case as typed by the user. LDAP synchronization will create person nodes with the userName, as provided from the LDAP server.
It is possible that LDAP synchronization can change the userName
associated with a Person
node. This can happen with a system that uses LDAP synchronization, or a system that creates person nodes on demand, or uses case-insensitive authentication. For example, Andy could log in as “Andy” and the associated Person node is created with the userName
“Andy.” Later, the LDAP synchronization runs and changes the userName
to “andy”.
Changes to Person
node userName
s will cause updates to other related data, such as ACL assignment.
Groups
Groups are collections of authorities with a name and display name.
Groups can include other groups or people. You can include a group in one or more other groups, as long as this inclusion does not create any cyclic relationships.
Zones
All person and group nodes are in one or more zones. You can use zones for any partitioning of authorities. For example, synchronization uses zones to record from which LDAP server users and groups have been synchronized. Zones are used to hide some groups that provide Role Based Access Control (RBAC) role-like functionality from the administration pages of Alfresco Share. Examples of hidden groups are the roles used in Share and Alfresco Records Management. Only users and groups in the default zone are shown for normal group and user selection on the group administration pages. Zones can’t be managed from the administration pages of Share.
Zones are intended to have a tree structure defined by naming convention. Zones are grouped into two areas: Application-related zones and authentication-related zones.
Within a zone, a group is considered to be a root group if it’s not contained by another group in the same zone.
Content Services uses a model for persisting people, groups, and zones. A Person node represents each person, and an AuthorityContainer
represents groups, which can be used for other authority groupings such as roles. AuthorityContainer and Person are sub-classes of Authority and as such can be in any number of Zones.
Application-related zones
Application-related zones, other than the default, hide groups that implement RBAC like roles. Application zones, by convention, start APP. and include:
APP.DEFAULT
is for person and group nodes to be found by a normal search. If no zone is specified for a person or group node, they will be a member of this default zone.APP.SHARE
is for hidden authorities related to Alfresco Share.APP.RM
will be added for authorities related to RM.
Authorization-related zones
Zones are also used to record the primary source of person and group information. They can be held within Content Services or some external source. While authorities can be in many zones, it makes sense for an authority to be in only one authentication-related zone.
AUTH.ALF
is for authorities defined within Content Services and not synchronized from an external source. This is the default zone for authentication.AUTH.EXT.<ID>
is for authorities defined externally, such as in LDAP.
Define permissions
Permissions and their groupings are defined in an XML configuration file.
The default file is found in the distribution configuration directory as permissionDefinitions.xml. This configuration can be replaced or extended.
The following example uses the permission definitions related to the Ownable
aspect.
<!-- ============================================== -->
<!-- Permissions associated with the Ownable aspect -->
<!-- ============================================== -->
<permissionSet type="cm:ownable" expose="selected">
<!-- Permission control to allow ownership of the node to be taken from others -->
<permissionGroup name="TakeOwnership" requiresType="false" expose="false">
<includePermissionGroup permissionGroup="SetOwner" type="cm:ownable" />
</permissionGroup>
<permissionGroup name="SetOwner" requiresType="false" expose="false"/>
<!-- The low level permission to control setting the owner of a node -->
<permission name="_SetOwner" expose="false" requiresType="false">
<grantedToGroup permissionGroup="SetOwner" />
<requiredPermission on="node" type="sys:base" name="_WriteProperties" />
</permission>
</permissionSet>
Permissions and permission groups are defined in a permission set, which is a sub-element of the permissions root element. A permission set is associated with a type or aspect and applies only to that type and sub-types, or aspect and sub-aspects.
A permission has a name. By convention, the names of permissions start with an underscore character. A permission, in its definition, can be granted to any number of permission groups. This means that those permission groups will include the permission. The permission might require that the type or aspect specified on the permission set be present on the node. If a permission is associated with an aspect and the requiresType property is set to true then if that aspect is not applied to a node, the permission does not apply to that node either. If an aspect-related permission definition has the requiresType property set to false, the permission applies to any node, even if the aspect has not been applied to the node.
An aspect can be applied at any time and there are no restrictions as to which aspects can be applied to a type. A permission might also require other permissions be tested on the same node, its children, or its parent. In the example, _SetOwner
requires _WriteProperties
. This means you can’t set ownership on a node if you’re not allowed to write to its properties. You can also use this to check that all children can be deleted before deleting a folder, or to enforce that you can only read nodes for which you can read all the parents; neither are normally required. The configuration to do this is present in the standard configuration file but is commented out. The _DeleteNode
permission definition (as shown in the following code snippet) is an example. If permission A
requires permission B
and this requirement is implied (by setting the implies attribute of the requiredPermission
element to true
), assigning an authority permission A
will also give them permission B
(as opposed to checking they have permission B
).
<permission name="_DeleteNode" expose="false" >
<grantedToGroup permissionGroup="DeleteNode" />
<!-- Commented out parent permission check ...
<requiredPermission on="parent" name="_ReadChildren" implies="false"/>
<requiredPermission on="parent" name="_DeleteChildren" implies="false"/>
<requiredPermission on="node" name="_DeleteChildren" implies="false"/>
-->
<!-- Recursive delete check on children -->
<!-- <requiredPermission on="children" name="_DeleteNode" implies="false"/> -->
</permission>
Permissions are normally hidden inside permission groups. Permission groups are made up of permissions and other permission groups. By convention, each permission has a related permission group. Permission groups can then be combined to make other permission groups. As for permissions, a permission group can be exposed by the administration pages of Alfresco Share and might require the presence of a type or aspect to apply to a particular node. In addition, a permission group can allow full control, which grants all permissions and permission groups. As a type or aspect can extend another, a permission group defined for a type or aspect can extend one defined for one of its parent types and be assigned more permissions, include more permission groups, or change what is exposed in the administration pages of the Alfresco Share web clients.
It is unusual to extend or change the default permission model unless you’re adding your own types, aspects, and related public services or you wish to make minor modifications to the existing behavior. The following code snippets show how to extend and replace the default permission model.
<bean id='permissionsModelDAO'
class="org.alfresco.repo.security.permissions.impl.model.PermissionModel" init-method="init">
<property name="model">
<-- <value>alfresco/model/permissionDefinitions.xml</value> -->
<value>alfresco/extension/permissionDefinitions.xml</value>
</property>
<property name="nodeService">
<ref bean="nodeService" />
</property>
<property name="dictionaryService">
<ref bean="dictionaryService" />
</property>
</bean>
The preceding code example shows how to replace the default permission model with one located in the alfresco/extension
directory. The following code snippet shows how to extend the existing model.
<bean id="extendPermissionModel" parent="permissionModelBootstrap">
<property name="model" value="alfresco/extension/permissionModelExtension.xml" />
</bean>
Control site creation permissions
By default, any authenticated user can create sites in Share. The creator of the new site is given the Site Manager
role and they control who has access to the site and in what role.
The beans that enforce security to the repository services based on the currently authenticated user are defined in the public-services-security-context.xml file.
-
Copy the following code and add it to the
<extension>/custom-model-context.xml
file.<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'> <beans> <bean id="SiteService_security" class="org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityInterceptor"> <property name="authenticationManager"><ref bean="authenticationManager"/></property> <property name="accessDecisionManager"><ref bean="accessDecisionManager"/></property> <property name="afterInvocationManager"><ref bean="afterInvocationManager"/></property> <property name="objectDefinitionSource"> <value> org.alfresco.service.cmr.site.SiteService.cleanSitePermissions=ACL_NODE.0.sys:base.ReadProperties org.alfresco.service.cmr.site.SiteService.createContainer=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.site.SiteService.createSite=ACL_METHOD.GROUP_SITE_CREATORS org.alfresco.service.cmr.site.SiteService.deleteSite=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.findSites=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.site.SiteService.getContainer=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.site.SiteService.listContainers=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.site.SiteService.getMembersRole=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.getMembersRoleInfo=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.resolveSite=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.getSite=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.site.SiteService.getSiteShortName=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.site.SiteService.getSiteGroup=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.getSiteRoleGroup=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.getSiteRoles=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.getSiteRoot=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.site.SiteService.hasContainer=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.hasCreateSitePermissions=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.hasSite=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.isMember=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.listMembers=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.listMembersInfo=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.listMembersPaged=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.listSiteMemberships=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.listSites=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.site.SiteService.listSitesPaged=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.site.SiteService.removeMembership=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.canAddMember=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.setMembership=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.updateSite=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.countAuthoritiesWithRole=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.isSiteAdmin=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.*=ACL_DENY </value> </property> </bean> </beans>
-
Modify the inserted
SiteService_security
bean to match your requirements. For example:To give permission to only Administrators to create site, change:
org.alfresco.service.cmr.site.SiteService.createSite=ACL_ALLOW
to
org.alfresco.service.cmr.site.SiteService.createSite=ACL_METHOD.ROLE_ADMINISTRATOR
where,
ACL_ALLOW
executes a method that allows access to all users andACL_METHOD.ROLE_ADMINISTRATOR
executes a method that allows access to users who are members of the administrator group. -
Save the file.
-
Restart Content Services.
Access Control Lists
An Access Control List (ACL) is an ordered list of one or more Access Control Entries (ACE). An ACE associates a single authority to a single permission group or permission, and states whether the permission is to be allowed or denied. All nodes have an associated ACL.
There is one special, context-free, ACL defined in the XML configuration to support global permissions. An ACL specifies if it should inherit ACEs from a parent ACL. The parent ACL is associated with the primary parent node. When a new node is created it automatically inherits all ACEs defined on the parent within which it is created. Linking a node to a secondary parent has no effect on ACE inheritance; the node will continue to inherit permission changes from its primary parent (defined when it was first created).
By default, ACL inheritance is always from the primary parent. The underlying design and implementation does not mandate this. ACL inheritance does not have to follow the parent child relationship. It is possible to change this through the Java API.
There are several types of ACL defined in ACLType. The main types are:
DEFINING
SHARED
FIXED
GLOBAL
A node will be associated with an ACL. It will have a DEFINING ACL if any ACE has been set on the node. DEFINING ACLs include any ACEs inherited from the node’s primary parent and above, if inheritance is enabled. All DEFINING ACLs are associated with one SHARED ACL. This SHARED ACL includes all the ACEs that are inherited from the DEFINING ACL. If the primary children of a node with a DEFINING ACL do not themselves have any specific ACEs defined then they can be assigned the related SHARED ACL. For the primary children of a node with a SHARED ACL that also have no specific ACEs set they can use the same SHARED ACL. A single SHARED ACL can be associated with many nodes. When a DEFINING ACL is updated, it’ll cascade update any related ACLs by using the ACL relationships rather than walk the node structure. If a DEFINING ACL inherits ACEs, then these will come from the SHARED ACL related to another DEFINING ACL.
ACLs and nodes have two linked tree structures.
FIXED ACLs are not associated with a node but found by name. A node ACL could be defined to inherit from a fixed ACL. A GLOBAL ACL is a special case of a FIXED ACL with a well known name. It will be used to hold the global ACE currently defined in XML.
ACEs comprise an authority, a permission, and a deny/allow flag. They are ordered in an ACL.
ACL ordering and evaluation
The ACEs within an ACL are ordered and contain positional information reflecting how an ACE was inherited. DEFINING ACLs have entries at even positions; SHARED ACLs have entries at odd positions. For a DEFINING ACL, any ACEs defined for that ACL have position 0, any inherited from the parent ACL have position two, and so on. For a SHARED ACL, ACEs defined on the ACL from which it inherits will have position one.
When Content Services makes permission checks, ACEs are considered in order with the lowest position first. Deny entries take precedence over allow entries at the same position. Once a deny entry is found for a specific authority and permission combination, any matching ACE, at a higher position from further up the inheritance chain, is denied. A deny for one authority does not deny an assignment for a different authority. If a group is denied Read
permission, a person who is a member of that group can still be assigned Read
permission using another group or directly with their person userName
. However, if an authority is granted Read
(made up of ReadContent
and ReadProperties
) and the same authority denied ReadContent
, they will just be granted ReadProperties
permission. The administration pages of Alfresco Share do not expose deny.
The default configuration is any deny denies
. This is set by adding the following property to the alfresco-global.properties
file:
security.anyDenyDenies=true
You can alter the configuration to support any allow allows
. This is set by adding the following property to the alfresco-global.properties
file:
security.anyDenyDenies=false
An ACL example
This example relates a tree of nodes to two corresponding trees of ACLs. The nodes in the node tree are identified by number and are shown filled in black if they have any ACEs set, or white/clear if not. Primary child relationships are drawn as black lines and secondary child relationships as dashed lines. ACLs in the ACL trees are identified by letter, DEFINING ACLs are shown filled in black, and SHARED ACLs are shown as clear. Under each node on the node tree the related ACL is referenced.
The table describes the ACEs in each ACL and their position.
ACL format | Authority | Permission | Allow/Deny | Position |
---|---|---|---|---|
ACL A (Defining, no inheritance) | All | Read | Allow | 0 |
ACL B (Shared, inherits from ACL A) | All | Read | Allow | 1 |
ACL C (Defining, inherits from ACL B) | All | Read | Allow | 2 |
ROLE_OWNER | All | Allow | 0 | |
GROUP_A | Write | Allow | 0 | |
GROUP_A | CreateChildren | Allow | 0 | |
ACL D (Shared, inherits from ACL C) | ALL | Read | Allow | 3 |
ROLE_OWNER | All | Allow | 1 | |
GROUP_A | Write | Allow | 1 | |
GROUP_A | CreateChildren | Allow | 1 | |
ACL E (Defining, inherits from ACL B) | All | Read | Allow | 2 |
Andy | All | Allow | 0 | |
Bob | Write | Allow | 0 | |
Bob | WriteContent | Deny | 0 | |
ACL F (Shared, inherits from ACL E) | All | Read | Allow | 3 |
Andy | All | Allow | 1 | |
Bob | Write | Allow | 1 | |
Bob | WriteContent | Deny | 1 | |
ACL G (Defining, no inheritance) | Bob | All | Allow | 0 |
ACL H (Shared, inherits from ACL G) | Bob | All | Allow | 1 |
ACL A, and any ACL that inherits from it, allows Read
for everyone (All) unless permissions are subsequently denied for everyone (All). If ACL A is changed, all the ACLs that inherit from ACL A in the ACL tree will reflect this change. In the example, nodes 1-12 would be affected by such a change. Nodes 13 and 14 would not inherit the change due to the definition of ACL G.
ACL C adds Contributor
and Editor
permissions for any authority in GROUP_A
.
Note: The
GROUP_
prefix is normally hidden by the administration pages of Alfresco Share.
Anyone in GROUP_A
can edit existing content or create new content. The owner ACE means that anyone who creates content then has full rights to it. The ACE assignment for owner is not normally required as all rights are given to node owners in the context-free ACL defined in the default permission configuration.
ACL E adds some specific user ACEs in addition to those defined in ACL A. As an example, it allows Bob Write
but also denies WriteContent
. Write
is made up of WriteContent
and WriteProperties
. Bob will only be allowed WriteProperties
.
ACL G does not inherit and starts a new ACL tree unaffected by any other ACL tree unless an inheritance link is subsequently made.
If a new node was created beneath node 13 or 14 it would inherit ACL H. If a new node was created beneath nodes 1, 6, 7, or 8 it would inherit ACL B.
If a node that has a shared ACL has an ACE set, a new defining ACL and a related shared ACL are inserted in the ACL tree. If a defining ACL has all its position 0 ACEs removed, it still remains a defining ACL: There is no automatic clean up of no-op defining ACLs.
Modify access control
Modifying access control can involve changing definitions, adding services, defining types and aspects, or adding definitions to new or existing security interceptors.
Main functions include:
- Changing the definition of existing security interceptors to check for different conditions
- Adding new public services and related security interceptors
- Defining new types and aspects and their related permissions
- Adding new definitions to the security interceptor by implementing an ACEGI
AccessDecisionVoter
and/orAfterInvocationProvider
(in extreme cases)
A few constraints and design patterns should be observed when modifying access control. Permissions apply to the node as whole. In particular, the same Read rights apply to all properties and content. You should check that methods can be executed and not that a user has a particular permission. The access control restrictions for a public service method can change. Follow the design pattern to implement RBAC roles.
When modifying access control, do not try to split ReadProperties
and ReadContent
. This does not make sense for search. A node and all of its properties, including content, are indexed as one entity. Splitting the evaluation of access for content and properties is not possible. Search would have to apply both criteria so as to not leak information. Other services, such as copy, might not behave as expected or might produce nodes in an odd state.
Permissions are assigned at the node level, not at the attribute level. Again, this makes sense with the search capabilities. Search results need to reflect what the user performing the search can see. It makes sense that all properties have the same Read
access as the node, as nodes are indexed for searching and not individual properties. Applying Read
ACLs at the property level would require a change to the indexing implementation or a complex post analysis to work out how nodes were found by the search. If not, the values of properties could be deduced by how a readable node was found from a search on restricted properties.
Fine grain attribute permissions could be implemented by using children nodes to partition metadata. Queries would have to be done in parts and joined by hand, as there is no native support for SQL-like join.
Check that method execution is allowed and not that the user has a fixed permission. Rather than checking for Read
permission in code, check that the appropriate method can be called using the PublicServiceAccessService
bean. This avoids hard coding to a specific permission implementation and is essential if you intend to mix records management and the content repository. The access restrictions for public service methods can change. The PublicServiceAccessService
bean allows you to test if any public service method can be invoked successfully with a given set of arguments. It checks all the entry criteria for the method and, assuming these have not changed, the method can be called successfully. The method call can still fail if the conditions for the returned object are not met or some security configuration has changed, such as an ACE is removed, a user is removed from a group, or the method fails for a non-authorization reason.
For those coming from an RBAC background, Content Services has roles in the RBAC sense only for limited internal use. To implement RBAC use zoned groups. These groups won’t appear in the administration pages of Alfresco Share as normal groups (unless you also add them to the APP.DEFAULT
zone) but can be used to assign users and groups to roles. This approach has been taken to support roles in Alfresco Share. To map RBAC terminology to Content Services: operations map to method calls on public service beans, objects map to method arguments including nodes (folders, documents, and so on). Users and permissions/privileges map directly. Content Services allows the assignment of permissions to users or groups.
By default, the owner of an object can manage any aspect of its ACL. Users with ChangePermissions
rights for a node can also change its ACL. If users have the ability to alter the ACL associated with an object, they can allow other users to do the same. There is no restriction on the permissions they can assign. The Content Services model supports liberal discretionary access control with multi-level grant. A user who can grant access can pass on this right without any restriction. In addition, anyone who can change permissions can carry out the revocation of rights: it’s not restricted to the original granter. Normally, when someone can perform an operation you would not expect it is because they own the node and therefore have all permissions for that node.
Public services
Security is enforced around public services. Web services, web scripts, Alfresco Share, WebDAV, FTP, CMIS, and more, all use public services, and therefore include security enforcement.
Public services are defined in public-services-context.xml.
Access control allows or prevents users or processes acting on behalf of a user, from executing service methods on a particular object by checking if the current user, or any of the authorities granted to the current user, has a particular permission or permission group, or that the user has a particular authority.
For example, on the NodeService
bean, the readProperties
method checks that the current user has Read
permission for the node before invoking the method and returning the node’s properties. On the SearchService
, the query
method results are restricted to return only the nodes for which a user has Read
permission.
Public services configuration
Security is enforced in the Spring configuration by defining proxies for each internal service implementation and adding a method interceptor to enforce security for each public service proxy. These interceptors also have other roles. When a method is called on a public service, the security interceptor is called before the method it wraps. At this stage, the interceptor can examine the function arguments to the method and check that the user has the appropriate rights for each argument in order to invoke the method. For example, a method delete(NodeRef nodeRef)
exists on the node service. The security interceptor can see the nodeRef
argument before the underlying delete(...)
method is called. If configured correctly, the interceptor could check that the current user has Delete
permission for the node. If they do not have the permission, a security exception is raised. If all the entry criteria are met, the method goes ahead.
In a similar manner, after a method has executed the interceptor can examine the returned object and decide if it should return it to the caller. For example, a search method could return a list of nodes. The security interceptor could filter this list for only those nodes for which the current user has Read
permission.
It is also possible to configure a method so that it can be called by all users, only by users with the admin role, or only by specific users or groups. This can also be enforced by the security method interceptor.
Access control interceptor definitions for public services are included in <installLocation>\tomcat\webapps\alfresco\WEB-INF\classes\alfresco\public-services-security-context.xml
along with any other supporting beans. This configuration file also defines the location from which the permission model is loaded. The interceptors are wired up to the public services in <installLocation>\tomcat\webapps\alfresco\WEB-INF\classes\alfresco\public-services-context.xml
. The public services are the only Spring beans to have access control.
Method-level security definition
Method access is defined in the normal ACEGI manner with some additions.
The beans required to support Spring ACEGI-based security around method invocation are defined in public-services-security-context.xml. This configures two specific beans: A voter that can authorize method execution based on the permissions granted to the current user for specific arguments to the method, and an after invocation provider to apply security to objects returned by methods.
For the following information detailing pre-conditions and post-conditions, these factors are all relevant:
-
<authority>
Represents an authority (user name or group).
-
<#>
Represents a method argument index.
-
<permission>
Represents the string representation of a permission.
Pre-conditions take one of the following forms:
-
ACL_METHOD.<authority>
Restricts access to the method to those with the given authority. This could be a user name, role or group. Dynamic authorities are not supported.
-
ACL_NODE.<#>.<permission>
Restricts access control to users who have the specified permission for the node at the identified argument. If the argument is a
NodeRef
, it’ll be used; if it is aStoreRef
, the root node for the store will be used; if it is aChildAssociationRef
, the child node will be used. -
ACL_PARENT.<#>.<permission>
Restricts access control to users who have the specified permission for the parent of the node on the identified argument. If the argument is a
NodeRef
, the parent of the node will be used; if it is aChildAssociationRef
, the parent node will be used. -
ROLE
Checks for an authority starting with
ROLE_
. -
GROUP
Checks for an authority starting with
GROUP_
.
Here are some examples of method level security parameters:
ACL_METHOD.ROLE_ADMINISTRATOR
: Executes a method that allows access to users who are members of the administrator group.ACL_ALLOW
: Executes a method that allows access to all users.
If more than one ACL_NODE.<#>.<permission>
, ACL_PARENT.<#>.<permission>
, or ACL_METHOD.<permission>
entry is present, then all of the ACL_NODE
and ACL_PARENT
permissions must be present and any one of the ACL_METHOD
restrictions, if present, for the method to execute.
Post-conditions take the forms:
-
AFTER_ACL_NODE.<permission>
Similar to
ACL_NODE.<#>.<permission>
but the restriction applies to the return argument. -
AFTER_ACL_PARENT.<permission>
Similar to
ACL_PARENT.<#>.<permission>
but the restriction applies to the return argument.
The support return types are:
StoreRef
ChildAssociationRef
- Collections of
StoreRef
,NodeRef
,ChildAssociationRef
, andFileInfo
FileInfo
NodeRef
- Arrays of
StoreRef
,NodeRef
,ChildAssociationRef
, andFileInfo
PagingLuceneResultSet
QueryEngineResults
ResultSet
The post-conditions will create access denied exceptions for return types such as NodeRef
, StoreRef
, ChildAssociationRef
, and FileInfo
. For collections, arrays, and result sets, their members will be filtered based on the access conditions applied to each member.
Continuing the example from the permissions defined for the Ownable
aspect, the definition for the security interceptor for the related OwnableService
is shown in the following code snippet.
<bean id="OwnableService_security"
class="org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityInterceptor">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="accessDecisionManager"><ref local="accessDecisionManager"/></property>
<property name="afterInvocationManager"><ref local="afterInvocationManager"/></property>
<property name="objectDefinitionSource">
<value>
org.alfresco.service.cmr.security.OwnableService.getOwner=ACL_NODE.0.sys:base.ReadProperties
org.alfresco.service.cmr.security.OwnableService.setOwner=ACL_NODE.0.cm:ownable.SetOwner
org.alfresco.service.cmr.security.OwnableService.takeOwnership=ACL_NODE.0.cm:ownable.TakeOwnership
org.alfresco.service.cmr.security.OwnableService.hasOwner=ACL_NODE.0.sys:base.ReadProperties
org.alfresco.service.cmr.security.OwnableService.*=ACL_DENY
</value>
</property>
</bean>
Security for the four methods on the OwnableService
is defined. To invoke the OwnableService getOwner()
method on a node, the invoker must have permission to read the properties of the target node. To set the owner of a node, a user must have been explicitly assigned the SetOwner
permission or have all rights to the node. A user can have all rights to a node by using the context-free ACL or be assigned a permission, which grants all permission or includes SetOwner
. With the default configuration, a user will own any node they create and therefore be able to give ownership to anyone else and possibly not have the right to take ownership back.
The last entry catches and denies access for any other method calls other than those listed. If any additional methods were added to this service and no security configuration explicitly defined for the new methods, these methods would always deny access.
Implementation and services
Content Services enforces security services for managing authentication information.
The following key services are involved in access control:
AuthenticationService
: responsible for authenticating user name and password.PersonService
: responsible for obtaining a reference to thePerson
node for a given user name. It also creates, deletes and updates personal information.AuthorityService
: responsible for managing authorities.PermissionService
: responsible for managing ACLs and ACEs, and for checking if a user has been assigned a permission for a particular node.OwnableService
: manages object ownership and is used in evaluation the dynamicROLE_OWNER
authority.
Let’s consider a possible scenario to understand how the security services work. A user logs in using the authentication service, which determines the user’s authorities, such as their user name (which is a USER
authority). The authority service adds and manages the relevant groups and roles. The permission service maps those users, groups and roles to operations on particular nodes. It also controls the inheritance of permissions and provides a common set of default permissions. The owner service is related to the special OWNER
role and it determines the owner of a node. The person service deals with the special case of person nodes, which identify users.
The protection of public services methods is implemented using the Spring method interceptors defined as part of the related ACEGI 0.8.2 security package. The Content Services implementation adds new implementations of the ACEGI interfaces AccessDecisionVoter
and AfterInvocationProvider
, which support the configuration elements that have already been described (for example, ACL_NODE.<#>.<permission>
). These extension classes make use of the key services.
Authentication service
Use this information to understand and configure authentication service.
The authentication service provides an API for:
- Authenticating using a user name and password
- Authenticating using a ticket
- Creating, updating and deleting authentication information
- Clearing the current authentication
- Invalidating a ticket
- Getting the user name for currently authenticated users
- Getting a ticket for subsequent re-authentication
The authenticated user name is used as the key to obtain other security information, such as group membership, the details about the person or to record a user as the owner of an object. It is one of the identifiers against which permissions can be assigned.
The authentication service does not provide any details about a user other than authentication. It stores authentication information on the calling thread. Application developers should ensure that this information is cleared.
The authentication service brings together three components:
- authentication component, which supports authentication;
- authentication DAO, which provides an API to create, delete and update authentication information; and
- ticket component, which manages and stores tickets that can be obtained after authentication and used in place of authentication.
The implementation and configuration for this service can be found in the authentication-services-context.xml
file. This default implementation coordinates two service providers for AuthenticationComponent
and MutableAuthenticationDAO
. It also uses the permission service provider interface to clear up permissions as users are deleted. Tickets are supported using the ticket component.
Configure multiple tickets for authentication
For each authentication attempt, Content Services returns a different session ID, but the same ticket for each user. You can configure multiple tickets using the authentication.ticket.useSingleTicketPerUser
option.
The TicketComponent
configuration setting, in alfresco-global.properties
, has an option called authentication.ticket.useSingleTicketPerUser
. This option has a default setting of true
, which means that only one ticket is created for each user, and this ticket is returned for every authentication attempt by that user. If the ticket is invalidated, the user is required to re-authenticate before using the repository.
To set multiple tickets for each user, set authentication.ticket.useSingleTicketPerUser=false
.
Person service
Use this information to understand and configure of person service.
The PersonService
interface is the API by which nodes of the person type, as defined in contentModel.xml, should be accessed.
The PersonService
is responsible for all of the following:
- Obtaining a reference to the Person node for a given user name
- Determining if a person entry exists for a user
- Potentially creating missing people entries with default settings on demand
- Supplying a list of mutable properties for each person
- Creating, deleting, and altering personal information
The beans to support the PersonService
and its configuration can be found in authentication-services-context.xml. The principle configuration options are around how people are created on demand if users are managed by using LDAP or some other external user repository.
Authority service
Use this information to understand and configure authority service, using the authority-services-context.xml
file.
The authority service is responsible for:
- Creating and deleting authorities
- Querying for authorities
- Structuring authorities into hierarchies
- Supporting queries for membership
- Finding all the authorities that apply to the current authenticated user
- Determining if the current authenticated user has admin rights
- Managing zones and the assignment of authorities to zones
The default implementation allows a list of group names to define both administration groups and guest groups. Each authentication component defines its own default administrative user(s), which can also be set explicitly. The default service is defined in the authority-services-context.xml file.
Using guestGroups and adminGroups properties
The authority-services-context.xml
, bean id authorityService
provides the property configuration of the Authority Service implementation. This configuration also allows the designation of specific groups with admin
or guest
permissions in the system.
By listing a group under the guestGroups
property (case insensitive), the users in that group will only be allowed guest
permission. Likewise, by listing a group under the adminGroups
property (case insensitive), the users in that group will be provided admin
permission.
For example, assume that you’re synchronizing users into Content Services and you specifically want to specify some groups as only guest users in the system. You would override the authority-services-context.xml
file adding those groups to the guestGroups
list (case insensitive). As a result, users in those groups will have authenticated logins but limited to guest authorization.
Configure guestGroups
and adminGroups
properties
Use this information to configure the guestGroups
and adminGroups
properties.
-
Download the authority-services-context.xml file.
-
Paste this file into the
<extension>
directory. -
Open the
authority-services-context.xml
file.-
To specify some groups as only guest users, add them to the
guestGroups
property list.<!-- A list of groups with guest rights. --> <!-* --> <property name="guestGroups"> <set> </set> </property>
-
To assign admin rights to some groups, add them to the
adminGroups
property list.<!-- A list of groups with admin rights. --> <!-* --> <property name="adminGroups"> <set> <value>ALFRESCO_ADMINISTRATORS</value> </set> </property>
-
-
Save the file and then restart the server.
Permission service
Use this information to understand and configure permission service.
The permission service is responsible for:
- Providing well known permissions and authorities
- Providing an API to read, set, and delete permissions for a node
- Providing an API to query, enable, and disable permission inheritance for a node
- Determining if the current, authenticated user has a permission for a node
The PermissionService
interface defines constants for well-known permissions and authorities.
The default implementation coordinates implementations of two service provider interfaces: a ModelDAO
and a PermissionsDAO
. A permission is simply a name scoped by the fully qualified name of the type or aspect to which it applies. The beans are defined and configured in public-services-security-context.xml. This file also contains the configuration for security enforcement.
The ModelDAO
interface defines an API to access a permissions model. The default permission model is in XML and defines permission sets, and their related permission groups and permissions. Global permissions are part of the permission model. There can be more than one permission model defined in XML; they’re in practice merged into one permission model. A module can extend the permission model.
The available permissions are defined in the permission model. This is defined in permissionDefinitions.xml. This configuration is loaded in a bean definition in public-services-security-context.xml. This file also defines global permissions. The definition file is read once at application start-up. If you make changes to this file, you’ll have to restart the repository in order to apply the changes.
Ownable service
Use this information to understand and configure ownable service.
The idea of file ownership is present in both UNIX and Windows. In Content Services, the repository has the concept of node ownership. This ownership is optional and is implemented as an aspect.
The owner of a node can have specific ACLs granted to them. Ownership is implemented as the dynamic authority, ROLE_OWNER
, and is evaluated in the context of each node for which an authorization request is made. The Ownable
aspect, if present, defines a node’s owner by storing a userName; if the Ownable
aspect is not present, the creator is used as the default owner. If the userName
of the current user matches, including case, the userName
stored as the owner of the node, the current user will be granted all permissions assigned to the authority ROLE_OWNER
.
The OwnableService
is responsible for all of the following:
- Determining the owner of a node
- Setting the owner of a node
- Determining if a node has an owner
- Allowing the current user to take ownership of a node
The OwnableService
is supported by an Ownable
aspect defined in <installLocation>\tomcat\webapps\alfresco\WEB-INF\classes\alfresco\model\contentModel.xml
.
There are permissions and permission groups associated with the Ownable aspect in the permission model and related access controls applied to the methods on the public OwnableService
.
Admin password in default authentication
The Admin user password is used by the default authentication system.
The Admin password for default authentication is set as a part of the initial bootstrap. This is located in config\alfresco\bootstrap\alfrescoUserStore.xml
. The password is MD4 encoded, as required by alfrescoNTLM
.
Note: Choose a strong, unique password for your admin account, and consider changing it regularly.
How to reset the Admin password?
If you lose or forget the password for the Admin user, you can reset the password in the database using one of the following methods:
-
If you know the password of at least one user, then:
-
Assign Admin rights to this known user by adding the following line in the
alfresco-global.properties
file.alfresco_user_store.adminusername=username
where,
username
is the user name of the user whose password is known. - Restart the repository.
- Login as the known user.
- Reset the Admin user’s password.
- Reset the configuration.
-
-
Reset the Admin password without knowing any user password:
- Configure the authentication component to accept all logins using
org.alfresco.repo.security.authentication.SimpleAcceptOrRejectAllAuthenticationComponentImpl
. - Login as a user with Admin rights.
- Reset the Admin user’s password.
- Revert the configuration.
- Configure the authentication component to accept all logins using
-
Change the password directly in the database:
Note: These steps works only for Content Services version 3.1 to 5.0.
-
Run the following command to find out the identifying parameters for how the Admin password is stored. Check that you’ve only one row in the output.
SELECT anp1.node_id, anp1.qname_id, anp1.string_value FROM alf_node_properties anp1 INNER JOIN alf_qname aq1 ON aq1.id = anp1.qname_id INNER JOIN alf_node_properties anp2 ON anp2.node_id = anp1.node_id INNER JOIN alf_qname aq2 ON aq2.id = anp2.qname_id WHERE aq1.local_name = 'password' AND aq2.local_name = 'username' AND anp2.string_value = 'admin'
The output shows the current MD4 hashed password for the Admin user. Here’s an example output:
+---------+----------+----------------------------------+ | node_id | qname_id | string_value | +---------+----------+----------------------------------+ | 4 | 10 | 209c6174da490caeb422f3fa5a7ae634 | +---------+----------+----------------------------------+ 1 row in set (0.00 sec)
-
To update the password, use the following command:
UPDATE alf_node_properties SET string_value='209c6174da490caeb422f3fa5a7ae634' WHERE node_id=THENODEIDABOVE and qname_id=THEQNAMEVALUEABOVE
Replace
THENODEIDABOVE
andTHEQNAMEVALUEABOVE
with the result values ofnode_id
andqname_id
, obtained in the previous step. In this example, it is4
and10
, respectively.Note: Ensure that you use appropriate
AND
conditions in theUPDATE
query. -
Restart Content Services.
-
-
Change the password directly in the database:
Note: These steps works only for Content Services version 5.1 onwards.
-
Run the following query to find out which encoder is being used to store the Admin password. Check that you’ve only one row in the output.
Note: You must encode the password using the result of the query.
SELECT anp1.node_id, anp1.qname_id, anp1.string_value FROM alf_node_properties anp1 INNER JOIN alf_qname aq1 ON aq1.id = anp1.qname_id INNER JOIN alf_node_properties anp2 ON anp2.node_id = anp1.node_id INNER JOIN alf_qname aq2 ON aq2.id = anp2.qname_id WHERE aq1.local_name = '**hashIndicator**' AND aq2.local_name = 'username' AND anp2.string_value = 'admin';
The output shows the current password encoding being used.
+---------+----------+--------------+ | node_id | qname_id | string_value | +---------+----------+--------------+ | 4 | 94 | **bcrypt10** | +---------+----------+--------------+ 1 row in set (0.01 sec)
If no rows are returned, set the password using the instructions shown above (md4 encoding).
If a row is returned, encode the password using the result of the query, which can either be md4 or sha256 or bcrypt10 encoding.
Run the following query to find the identifying parameters for how the Admin password is stored.
SELECT anp1.node_id, anp1.qname_id, anp1.string_value FROM alf_node_properties anp1 INNER JOIN alf_qname aq1 ON aq1.id = anp1.qname_id INNER JOIN alf_node_properties anp2 ON anp2.node_id = anp1.node_id INNER JOIN alf_qname aq2 ON aq2.id = anp2.qname_id WHERE aq1.local_name = '**passwordHash**' AND aq2.local_name = 'username' AND anp2.string_value = 'admin';
The output shows the current hashed password for the Admin user. Here’s an example output:
+---------+----------+--------------------------------------------------------------+ | node_id | qname_id | string_value | +---------+----------+--------------------------------------------------------------+ | 4 | 93 |**$2a$10$dq/2zNUA.MmECYipl1WMoOyGHYbaygh23PUa3Ox5xDHH7Z0guqF42**| +---------+----------+--------------------------------------------------------------+ 1 row in set (0.00 sec)
-
To update the password, use the following command:
UPDATE alf_node_properties SET string_value='209c6174da490caeb422f3fa5a7ae634' WHERE node_id=THENODEIDABOVE and qname_id=THEQNAMEVALUEABOVE
Replace
THENODEIDABOVE
andTHEQNAMEVALUEABOVE
with the result values ofnode_id
andqname_id
, obtained in the previous step. In this example, it is4
and93
, respectively.Note: Ensure that you use appropriate
AND
conditions in theUPDATE
query. -
Restart Content Services.
-
Security policies and filters
You can configure a number of policies and filters in Alfresco Share to mitigate security attacks. You can also configure filters in Alfresco Repository to mitigate security attacks when the Content Services ReST API is accessed externally.
The Open Web Application Security Project (OWASP) describes Cross-Site Request Forgery (CSRF) as a type of attack that occurs when a malicious web site, email, blog, instant message, or program causes a user’s web browser to perform an unwanted action on a trusted site for which the user is currently authenticated (see the Cross-Site_Request_Forgery Prevention_Cheat_Sheet).
The Share application must be accessible on the network to be available to users, and so it is protected with a CSRF filter. You should then also ensure that /alfresco
is protected behind a firewall. If another user interface client is used (that is, not Share), such as an ADF application that directly accesses the Content Services ReST API, then /alfresco
needs to be protected with a CSRF filter.
If you want to protect those areas against CSRF attacks, then you’ll need to implement a solution similar to one of those listed in the CSRF prevention cheat sheet. Of particular interest is a solution based on Apache with mod_csrf
because of efficiency and its loose coupling with the applications to protect.
Alfresco Share Security policies and filters
You can configure a number of policies and filters in Alfresco Share to mitigate security attacks.
The Share application must be accessible on the network to be available to users, and so it should be protected with a CSRF filter. You should then also ensure that /alfresco
is protected behind a firewall. If another user interface client is used (that is, not Share), such as an ADF application, then you need to also protect the Alfresco Repository with a CSRF filter.
Cross-Site Request Forgery (CSRF) filters for Share
You can configure CSRFPolicy
in Alfresco Share to prevent CSRF attacks that allow malicious requests to be unknowingly loaded by a user.
You can configure the CSRF filter to run with third party plugins and to stop specific repository services from being accessible directly through the Share proxy.
The filter is implemented in the org.alfresco.web.site.servlet.CSRFFilter
that reads the CSRFPolicy
configuration section in share-security-config.xml
.
CSRFPolicy
describes how and when the filter mitigates CSRF attacks:
- Each logged in user receives a secret CSRF token
- The token is communicated to the browser using a
Alfresco-CSRF-Token
cookie - When a logged in user performs a POST, PUT or DELETE HTTP request against Share the token must be passed in the request using one of the following methods:
- As a custom HTTP request header called
Alfresco-CSRF-Token
- As a URL parameter called
Alfresco-CSRF-Token
> Note: Usually the header is required, but in some circumstances a header can’t be used and in this case the token can be passed using a URL parameter. The default configuration only accepts the URL parameter when theContent-Type
header starts withmultipart/
.
- As a custom HTTP request header called
- Every time the logged in user visits a new Share page the token is renewed
- The filter checks that the referrer and original HTTP request headers match the current domain (if this is present in the request)
Do I need to alter my custom code?
Generally, you should not need to alter your custom code, for example, the following cases need no code alteration:
- You are reading data using GET requests only
- You are using the standard
Alfresco.util.Ajax
,alfresco/core/CoreXhr
orAlfresco.forms.Form
JavaScript classes when creating, updating or deleting data - You are writing a non-browser client (for example, a mobile application)
However, in these situations you’ll need to alter your code:
-
You are making an
XMLHttpRequest
with POST, PUT or DELETE methods without using theAlfresco.util.Ajax
oralfresco/core/CoreXhr
classes. If you’re using the nativeXMLHttpRequest
object or a third party library such as jQuery, add code to pass the token, for example:if (Alfresco.util.CSRFPolicy && Alfresco.util.CSRFPolicy.isFilterEnabled()) { xhrHeadersObject[Alfresco.util.CSRFPolicy.getHeader()] = Alfresco.util.CSRFPolicy.getToken(); }
If you’re using
YAHOO.util.DataSource
to load data with POST requests, add code similar to this example:if (Alfresco.util.CSRFPolicy && Alfresco.util.CSRFPolicy.isFilterEnabled()) { yuiDataSource.connMgr.initHeader(Alfresco.util.CSRFPolicy.getHeader(), Alfresco.util.CSRFPolicy.getToken(), false); }
-
You are making a form upload with enctype
multipart/form-data
without usingAlfresco.forms.Form
.When you upload a file by submitting a form with enctype
multipart/form-data
it’s not possible to set a header on the request because it’s not possible to set a header on any form submission in the browser. Pass the token as a URL parameter instead. If you’re using theAlfresco.forms.Form
class, this is handled for you automatically, otherwise add the token as a URL parameter, for example:if (Alfresco.util.CSRFPolicy && Alfresco.util.CSRFPolicy.isFilterEnabled()) { url += "?" + Alfresco.util.CSRFPolicy.getParameter() + "=" + encodeURIComponent(Alfresco.util.CSRFPolicy.getToken()); }
-
You are using a Flash movie inside Share to send HTTP requests with method POST.
If you’re using a Flash movie to upload files, using the
flash.net.FileReference ActionScript
class to perform a multipart/form-data request, add the token as a URL parameter in your Javascript before passing in the URL to the Flash movie. If your Flash movie is performing application/json or other text based POST requests, using theflash.net.URLRequest and/or flash.net.navigateToURL
ActionScript classes and methods, pass the token and the name of the header so that it can be set from the Flash movie.
When else might I need to make code updates
If servers from other domains are allowed to POST requests to your system, then you need to reconfigure CSRFPolicy
in your share-config-custom.xml
file so that the token or header is not checked:
- Copy the
CSRFPolicy
configuration inshare-security-config.xml
. -
Paste the configuration into your
share-config-custom.xml
file, ensuring that it is replacing the old configuration section:<config evaluator="string-compare" condition="CSRFPolicy" **replace="true"**>
-
Copy the following code and add it as the first child of the
<filter>
element:<rule> <request> <method>POST</method> <path>/page/trusted/call/1|/page/trusted/call/2</path> </request> <action name="assertReferer"> <param name="always">false</param> <param name="referer">https://www.trustedserver.com/.*</param> </action> <action name="assertOrigin"> <param name="always">false</param> <param name="origin">https://www.trustedserver.com</param> </action> </rule>
The CSRF filter compares the incoming request with the rule request elements to find one that matches and then invokes the defined actions for that rule before normal Share processing begins.
If you want to completely block certain services in the repository, you can add these URLs to the CSRF filter:
- Copy the
CSRFPolicy
configuration inshare-security-config.xml
. -
Paste the configuration into your
share-config-custom.xml
file, ensuring that it is replacing the old configuration section:<config evaluator="string-compare" condition="CSRFPolicy" replace="true">
-
Copy the following code and add it as the first child of the
<filter>
element:<rule> <request> <path>/proxy/alfresco/acme/special/services/.*</path> </request> <action name="throwError"> <param name="message">It is not allowed to access this url from your browser</param> </action> </rule>
Iframes and phishing attack mitigation
You can configure IFramePolicy
to protect users against a phishing attack, which attempts to acquire information such as user names or passwords by simulating a trustworthy entity.
Content Services allows you to control which domain pages or content are included in Alfresco Share to create a whitelist of allowed domains. A whitelist is a list of email addresses or IP addresses that are considered to be safe for use within your organization.
This IFramePolicy
is applied when Share includes an <iframe>
tag while constructing the Web View dashlet. The dashlet will allow only those URLs that have been added to the whitelist. Developers can use the Alfresco.util.IFramePolicy.isUrlAllowed()
method to check if a URL is allowed for custom implementations of a Web View or <iframe>
tag is included.
Note: If you have a previous installation which includes a URL from a third-party domain, you’ll get an error message in your production environment prompting you to configure your
IFramePolicy
configuration by adding the domain to the whitelist.
Note: URLs pointing to the same domain, such as documents or wiki pages inside Share, will continue to work as usual by default.
The whitelist of allowed domains is set in the <configRootShare>/classes/alfresco/share-security-config.xml
configuration file:
<config evaluator="string-compare" condition="IFramePolicy">
<same-domain>allow</same-domain>
<cross-domain>
<url>*</url>
</cross-domain>
</config>
To deny URLs from the current domain, override the existing code in the share-config-custom.xml
file with the following code:
<config evaluator="string-compare" condition="IFramePolicy" replace="true">
<same-domain>deny</same-domain>
</config>
To allow all cross domain URLs, override the existing code in the share-config-custom.xml
file with the following code:
<config evaluator="string-compare" condition="IFramePolicy" replace="true">
<cross-domain>
<url>*</url>
</cross-domain>
</config>
To allow specific cross domain URLs, override the existing code in the share-config-custom.xml
file with the following code:
<config evaluator="string-compare" condition="IFramePolicy" replace="true">
<cross-domain>
<url>https://www.owasp.org/</url>
</cross-domain>
</config>
Security filters and clickjacking mitigation
You can configure a security filter, SecurityHeadersPolicy
, that mitigates clickjacking attacks in Alfresco Share.
SecurityHeadersPolicy
is a Java Servlet filter that applies HTTP response headers to incoming requests in Share. The headers that are returned are defined in a configuration section called SecurityHeadersPolicy
in alfresco-security-config.xml
.
Three headers are added by default; X-Frame-Options
, X-Content-Type-Options
and X-XSS-Protection
:
<config evaluator="string-compare" condition="SecurityHeadersPolicy">
<headers>
<header>
<name>**X-Frame-Options**</name>
<value>SAMEORIGIN</value>
</header>
<header>
<name>**X-Content-Type-Options**</name>
<value>nosniff</value>
</header>
<header>
<name>**X-XSS-Protection**</name>
<value>1; mode=block</value>
</header>
</headers>
</config>
X-Frame-Options header
Adding this header to an HTTP response tells the browser whether Share pages are permitted inside iframes. In our default configuration we have set this to SAMEORIGIN
which means that Share pages are only permitted inside iFrames inside Share or in other web applications that live under the same domain. For example, it is possible to include http://www.acme.com/share
inside an iframe on http://www.acme.com/portal
.
You can override the configuration and set the header to return DENY
instead, by placing the following configuration in your share-config-custom.xml
file:
<config evaluator="string-compare" condition="SecurityHeadersPolicy">
<headers>
<header>
<name>X-Frame-Options</name>
<value>**DENY**</value>
</header>
</headers>
</config>
X-Content-Type-Options
This header is valid for Internet Explorer (IE) only. Older versions of IE (8 and below) sniff the content of a returned resource and then execute the content as the content type that IE thinks the resource has, instead of the content type that the server returned. To stop IE from doing this, nosniff
is returned in the header.
X-XSS-Protection
This header is provided by Internet Explorer (IE) to rectify sanitization logic that can be used by an attacker to introduce an XSS flaw on your site.
By default Share returns 1; mode=block
for this header, which stops IE from executing sanitized code.
It is also possible to set the value to 0
which stops IE from inspecting the code for XSS attacks.
Adding other headers
Content Services supports adding other headers to the configuration, for example, the Strict-Transport-Security
header forces the browser to allow only https
communication. This header is not provided by Share, but can be added by using this code:
<config evaluator="string-compare" condition="SecurityHeadersPolicy">
<headers>
<header>
<name>**Strict-Transport-Security**</name>
<value>**max-age=31536000**</value>
</header>
</headers>
</config>
Alfresco repository security policies and filters
You can configure filters in the repository to mitigate security attacks when the Content Services ReST API is accessed externally.
The Content Services ReST API must be accessible on the network when the user interface is implemented with the Alfresco Application Development Framework (ADF). The /alfresco
URL path then needs to be protected with a CSRF filter.
Cross-Site Request Forgery (CSRF) filters for Repository
You can configure the repository in Content Services with a filter to prevent CSRF attacks that allow malicious requests to be unknowingly loaded by a user.
Note: The CSRF filter will work correctly only if the Content Services server is configured to use HTTPS.
The CSRF filter can be configured in the web-client-security-config.xml
file, which is located in the alfresco.war
file. In most cases the only thing that needs to be modified is a regular expression that checks the Origin and Referer headers. This regular expression can be configured using the alfresco-global.properties
file. The property configuration then overrides the values in web-client-security-config.xml
. The following is an example configuration where Content Services runs on the mydomain.com
host and port 80
:
# CSRF filter overrides
csrf.filter.enabled=true
csrf.filter.referer=https://mydomain.com/.*
csrf.filter.referer.always=false
csrf.filter.origin=https://mydomain.com
csrf.filter.origin.always=false
The Origin
header will be present in HTTP requests that originate from an HTTPS URL and it’ll tell you from where the application (such as an ADF application) was loaded. If a non-standard port is used, such as 8443
, then you’ll have to include the port number, such as https://mydomain.com:8443/
. If the Origin
header is present, then it’s checked to make sure it matches the target origin (csrf.filter.origin
). If the Origin
header isn’t present, verify that the hostname in the Referer
header matches the target origin (that is, csrf.filter.referer
).
The csrf.filter.origin.always
property is a boolean that controls whether the referer/origin header must be present when validated. Some browsers don’t set referer due to privacy issues. Some old browsers don’t set origin.
To disable the CSRF filter all together set the csrf.filter.enabled
property to false
.