How-to guide
Secure servers
Secure the server: endpoints, certificates, users, roles and access control lists, audit events, and a checklist for production.
How-to guide
Secure servers
This page brings together all mechanisms a server uses to protect itself and its data. The default values of the OPC UA .NET SDK are designed for an easy start and are not suitable for production. The section Harden for production summarizes which settings you should change for production. The pages Understand certificates and Users and roles describe the fundamentals.
Default values at a glance
Types usedOpcServerOpcServerSecurityOpcServerBindingOpcCertificateStoresOpcAuthenticationLimitsOpcOperationLimits
| Setting | Default value | Recommendation for production |
|---|---|---|
Security. | empty: the server offers only endpoints with the mode None | only policies with SignAndEncrypt and Basic256Sha256, Aes128_Sha256_Rsa, or Aes256_Sha256_Rsa |
Binding. | empty: the server binds to all IPv4 and IPv6 addresses of the computer, even if its address is localhost | restrict to the network interfaces that clients use |
Security. | true: the server accepts unknown client certificates | false; add client certificates to the TrustedPeerStore in advance |
Security. | Default: the server accepts certificates signed with SHA-1, and backward-compatible validation is active | RejectSHA1Signed and Disable |
Security. | active as long as no other access control list is active | disable; allow known users only |
Security. | false: the server reports no audit events | true if needed (see Audit events) |
Certificate | true: if no certificate exists, the server creates a new one (up to version 2.58 also if the existing one is invalid) | false, and use a managed certificate |
| Automatically created certificate | self-signed, key length 2048 bits, valid for 600 months | certificate from your own certificate authority with a lifetime according to your policy |
Authentication | active: after 5 failed attempts within 5 seconds, an increasing delay starting at 1 second, at most 30 seconds | adjust to the number of users |
OperationLimits. | false: the server reports no per-request limits to its clients | true |
Transport | MaxMessageSize 10 MB, Max 5 MB, MaxStringLength and MaxArrayLength 65535 each | increase only as far as the application needs |
Insecure default values
With the default values, a server is reachable through all network interfaces of the computer, communicates without encryption, allows anonymous users, and accepts the certificate of every client it does not know. Anyone who can reach the computer over the network can then read and write data and call methods. Change these settings before you go to production.
Secure endpoints and the network
Types usedOpcServerOpcServerSecurityOpcSecurityPolicyOpcServerBinding
Offer encrypted endpoints only: As long as the EndpointPolicies property is empty, the server offers only endpoints with the mode None. Through these endpoints, it transfers data neither signed nor encrypted. As soon as you add a policy, the endpoint with the mode None is no longer offered. Therefore, add only policies with the mode SignAndEncrypt, and specify the algorithm explicitly. If you specify none (OpcSecurityAlgorithm.Auto), the SDK chooses Basic256Sha256. The section Endpoints describes how the server combines addresses and policies into endpoints.
Consider the TLS versions for HTTPS: As of version 2.59, the server lets the operating system choose the TLS versions for endpoints with the https scheme. Up to version 2.58, TLS 1.0, 1.1, and 1.2 were fixed. Clients that support only TLS 1.0 or 1.1 may therefore no longer be able to connect over HTTPS.
Bind only to the network interfaces you need: The address of the server (for example opc.tcp://localhost:4840/) determines the endpoints the server reports to its clients. It does not determine the network interfaces to which the server binds its ports. Unless you specify otherwise, the server binds to all IPv4 and IPv6 addresses of the computer. This makes it reachable from the network even if its address is localhost. Restrict the binding to the addresses you need with the Binding property (see Bind to network interfaces). Exclusive binding (UseExclusiveAddress), which is active by default, also prevents another process from using the same port.
Validate and trust client certificates
Types usedOpcServerOpcServerSecurityOpcCertificateValidationFailedEventArgsOpcCertificateValidationRulesOpcCertificateStoresOpcCertificateStoreInfo
When it connects, a client sends its certificate to the server for authentication. Based on the client certificate, the server decides whether it allows the connection with the client and thus trusts it.
Accept trusted certificates only: If the server should accept only trusted certificates, disable the default acceptance of all unknown certificates. The server does not automatically accept expired, revoked, or otherwise invalid certificates, even without this setting:
server.Security.AutoAcceptUntrustedCertificates = false;server.Security.AutoAcceptUntrustedCertificates = FalseValidate certificates yourself: Once you have disabled the default acceptance of all certificates, you should provide custom validation of the certificates:
server.CertificateValidationFailed += HandleCertificateValidationFailed;
...
private void HandleCertificateValidationFailed(object sender, OpcCertificateValidationFailedEventArgs e)
{
if (e.Certificate.Thumbprint == "...")
e.Accept = true;
}AddHandler server.CertificateValidationFailed, AddressOf HandleCertificateValidationFailed
'...
Private Sub HandleCertificateValidationFailed(sender As Object, e As OpcCertificateValidationFailedEventArgs)
If e.Certificate.Thumbprint = "..." Then
e.Accept = True
End If
End SubTrust a certificate manually: If the server has classified a client certificate as untrusted, you can declare it trusted manually. To do this, save it in the TrustedPeerStore:
// In context of the event handler the sender is an OpcServer.
var server = (OpcServer)sender;
if (!server.CertificateStores.TrustedPeerStore.Contains(e.Certificate))
server.CertificateStores.TrustedPeerStore.Add(e.Certificate);' In context of the event handler the sender is an OpcServer.
Dim server As OpcServer = CType(sender, OpcServer)
If Not server.CertificateStores.TrustedPeerStore.Contains(e.Certificate) Then
server.CertificateStores.TrustedPeerStore.Add(e.Certificate)
End IfStricter rules for certificate validation: The CertificateValidationRules property sets them. By default, the server also accepts certificates signed with SHA-1:
server.Security.CertificateValidationRules
= OpcCertificateValidationRules.RejectSHA1Signed
| OpcCertificateValidationRules.DisableBackwardsCompatibleValidation;server.Security.CertificateValidationRules _
= OpcCertificateValidationRules.RejectSHA1Signed _
Or OpcCertificateValidationRules.DisableBackwardsCompatibleValidationProtect your own certificate
Types usedOpcServerOpcCertificateManagerOpcCertificateSettingsOpcCertificateStoresOpcCertificateRequestedEventArgs
The section Application certificate describes how a certificate is loaded, created, and stored. For security, the following points matter:
- Properties of the certificate: A certificate created by the SDK is self-signed, uses a 2048-bit key, and is valid for 600 months. You set the key length (
KeySize), the validity in months (Lifetime), and the domains under which the server is reachable (DomainNames) with theOpcCertificateSettingsclass. For a server, the SDK adds the fully qualified domain name (FQDN) of the machine to the domains if DNS resolves it. Clients that connect by this name then accept the certificate. As the organization (O) in the subject, the SDK enters the manufacturer fromOpcServerInfo.CompanyName(propertyOrganization). It does not change an existing certificate. For production, use a certificate issued by the certificate authority of your company. Clients then only have to trust the certificate authority. - Do not replace a certificate silently: If the server finds no valid certificate at startup, for example because it has expired, it raises the
CertificateRequestedevent. Up to version 2.58, the following applies: If no handler provides a certificate and theAutoCreateCertificateproperty is active, the server deletes the previous certificate from theApplicationStoreand creates a new, self-signed certificate. Clients that trusted the previous certificate then reject the server. As of version 2.59, the server never replaces an existing but invalid certificate automatically. TheReasonproperty of the event data then states the reason:Invalid,UseNotAllowed,Revoked,TimeInvalid,IssuerUseNotAllowed,IssuerRevoked, orIssuerTimeInvalid. For an expired certificate, for example, it isTimeInvalid. TheAutoCreateCertificateproperty of the event data isfalsein this case. Unless the handler provides another certificate, the server keeps using the existing certificate. As of version 2.59, the server uses a certificate that the handler provides through theAlternativeCertificateproperty at startup. The certificate must contain its private key. Provide a certificate yourself through theCertificateRequestedevent and disable automatic creation. - Protect the private key: The
ApplicationStorecontains the certificate including its private key. The SDK does not set access permissions for the files of the certificate stores. Place the certificate stores in a directory that only the user account of the server can access. Load an existing certificate from a password-protected.pfxfile.
Authenticate users
Types usedOpcServerOpcUserIdentityOpcServerIdentityOpcCertificateIdentityOpcServerSecurityOpcAccessControlListOpcAnonymousAclOpcUserNameAclOpcCertificateAclOpcAccessControlEntryOpcOperationTypeOpcRequestTypeOpcAccessControlMode
By default, a server also allows access without a specific user identity. This kind of user authentication is called anonymous authentication. If a client specifies a user identity, however, the server must know this identity. Only then does it allow access with this identity. If users should identify themselves, for example with a user name and password or with a certificate, configure and enable the corresponding access control lists (ACL). Configuring the access control lists includes configuring the access control entries (ACE). Each entry represents a principal with a specific identity, that is, a user name and password or a certificate. You add the entries to the lists.
The access control list for anonymous users is active only as long as no other access control list is active. For example, if you enable the access control list for user names and passwords, anonymous access is switched off automatically. If it should still be possible, explicitly set the IsEnabled property of the AnonymousAcl to true. Conversely, the following example explicitly switches off anonymous access:
Disable the anonymous access control list:
server.Security.AnonymousAcl.IsEnabled = false;server.Security.AnonymousAcl.IsEnabled = FalseConfigure the access control list for user names and passwords:
var acl = server.Security.UserNameAcl;
acl.AddEntry("username1", "password1");
acl.AddEntry("username2", "password2");
acl.AddEntry("username3", "password3");
...
acl.IsEnabled = true;Dim acl = server.Security.UserNameAcl
acl.AddEntry("username1", "password1")
acl.AddEntry("username2", "password2")
acl.AddEntry("username3", "password3")
'...
acl.IsEnabled = TrueConfigure the access control list for certificates:
var acl = server.Security.CertificateAcl;
acl.AddEntry(new X509Certificate2(@".\user1.der"));
acl.AddEntry(new X509Certificate2(@".\user2.der"));
acl.AddEntry(new X509Certificate2(@".\user3.der"));
...
acl.IsEnabled = true;Dim acl = server.Security.CertificateAcl
acl.AddEntry(New X509Certificate2(".\user1.der"))
acl.AddEntry(New X509Certificate2(".\user2.der"))
acl.AddEntry(New X509Certificate2(".\user3.der"))
'...
acl.IsEnabled = TrueIf a custom X509CertificateValidator is set through the CertificateValidator property of the CertificateAcl, the server additionally validates the certificate of a user identity before it compares the thumbprint.
All access control lists provided by the SDK use the access control mode Whitelist. In this mode, every entry has access to all types of requests simply because it is defined. This also applies if access to them was not explicitly allowed. Therefore, deny the entries all operations that are not permitted. You set the allowed and denied operations directly on the entry that the access control list returns when you add it. The sections Access control modes, Roles, and Custom access control lists describe other modes, roles, and custom access control lists.
1. Keep a reference to an access control entry:
var user1 = acl.AddEntry("username1", "password1");Dim user1 = acl.AddEntry("username1", "password1")2. Deny the access control entry two rights:
user1.Deny(OpcRequestType.Write);
user1.Deny(OpcRequestType.HistoryUpdate);user1.Deny(OpcRequestType.Write)
user1.Deny(OpcRequestType.HistoryUpdate)3. Allow a previously denied right again:
user1.Allow(OpcRequestType.HistoryUpdate);user1.Allow(OpcRequestType.HistoryUpdate)Store passwords securely
Types usedOpcServerIdentityOpcUserNameAcl
If you add a user with a password as a string (AddEntry(userName, password)), the server keeps the password in memory in plain text. If the passwords of your users already exist as hash values, derive from the OpcServerIdentity class. The constructor takes the stored hash value as a byte array. The overridden TransformPassword method computes the hash value of the password sent at logon with the same algorithm. The server compares the two values in constant time. The following example uses PBKDF2 with a salt per user:
public sealed class HashedServerIdentity : OpcServerIdentity
{
private const int Iterations = 100000;
private readonly byte[] salt;
public HashedServerIdentity(string userName, byte[] passwordHash, byte[] salt)
: base(userName, password: passwordHash)
{
this.salt = salt;
}
public static byte[] Hash(string password, byte[] salt)
{
using (var pbkdf2 = new Rfc2898DeriveBytes(
password,
salt,
Iterations,
HashAlgorithmName.SHA256)) {
return pbkdf2.GetBytes(32);
}
}
protected override byte[] TransformPassword(string password)
{
return Hash(password, this.salt);
}
}Public NotInheritable Class HashedServerIdentity
Inherits OpcServerIdentity
Private Const Iterations As Integer = 100000
Private ReadOnly salt As Byte()
Public Sub New(ByVal userName As String, ByVal passwordHash As Byte(), ByVal salt As Byte())
MyBase.New(userName, password:=passwordHash)
Me.salt = salt
End Sub
Public Shared Function Hash(ByVal password As String, ByVal salt As Byte()) As Byte()
Using pbkdf2 = New Rfc2898DeriveBytes( _
password, _
salt, _
Iterations, _
HashAlgorithmName.SHA256)
Return pbkdf2.GetBytes(32)
End Using
End Function
Protected Overrides Function TransformPassword(ByVal password As String) As Byte()
Return Hash(password, Me.salt)
End Function
End ClassTo add the identity:
// Hash and salt come from the user store of the application.
var identity = new HashedServerIdentity("operator", storedHash, storedSalt);
server.Security.UserNameAcl.AddEntry(identity);
server.Security.UserNameAcl.IsEnabled = true;'Hash and salt come from the user store of the application.
Dim identity = New HashedServerIdentity("operator", storedHash, storedSalt)
server.Security.UserNameAcl.AddEntry(identity)
server.Security.UserNameAcl.IsEnabled = TrueValidate user certificates
If a user logs on with a certificate, the server compares its thumbprint with the entries of the CertificateAcl. If the server should validate the certificate first, for example against the certificate authority of your company and for revocation, set a custom X509CertificateValidator with the CertificateValidator property (namespace System.IdentityModel.Selectors). If the validator rejects the certificate with an exception, the server denies the logon:
public sealed class UserCertificateValidator : X509CertificateValidator
{
public override void Validate(X509Certificate2 certificate)
{
using (var chain = new X509Chain()) {
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
if (!chain.Build(certificate))
throw new SecurityException("The user certificate is not trusted.");
}
}
}Public NotInheritable Class UserCertificateValidator
Inherits X509CertificateValidator
Public Overrides Sub Validate(ByVal certificate As X509Certificate2)
Using chain = New X509Chain()
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online
If Not chain.Build(certificate) Then
Throw New SecurityException("The user certificate is not trusted.")
End If
End Using
End Sub
End Classserver.Security.CertificateAcl.CertificateValidator = new UserCertificateValidator();
server.Security.CertificateAcl.AddEntry(userCertificate);
server.Security.CertificateAcl.IsEnabled = true;server.Security.CertificateAcl.CertificateValidator = New UserCertificateValidator()
server.Security.CertificateAcl.AddEntry(userCertificate)
server.Security.CertificateAcl.IsEnabled = TrueRestrict users to endpoints
Types usedOpcAccessControlEntryOpcEndpointIdentityOpcAccessControlMode
Besides operations, an access control entry can also define the endpoints through which a user may log on. The access control mode for endpoints determines how the server evaluates allowed (Enable) and denied (Disable) endpoints. You specify it as the second mode when you add an entry. In the Blacklist mode, the user may log on only through explicitly allowed endpoints. Through all other endpoints, the server denies the logon with the status BadUserAccessDenied. This lets you restrict a service account to an endpoint with the mode SignAndEncrypt, for example. The address and policy of the OpcEndpointIdentity must match the endpoint through which the client connects:
var endpoint = new OpcEndpointIdentity(
"opc.tcp://192.168.0.10:4840/",
new OpcSecurityPolicy(OpcSecurityMode.SignAndEncrypt, OpcSecurityAlgorithm.Basic256Sha256));
// Endpoint access mode Blacklist: only explicitly enabled endpoints are permitted.
var service = server.Security.UserNameAcl.AddEntry(
"service",
servicePassword,
operationAccessMode: OpcAccessControlMode.Whitelist,
endpointAccessMode: OpcAccessControlMode.Blacklist);
service.Enable(endpoint);Dim endpoint = New OpcEndpointIdentity( _
"opc.tcp://192.168.0.10:4840/", _
New OpcSecurityPolicy(OpcSecurityMode.SignAndEncrypt, OpcSecurityAlgorithm.Basic256Sha256))
'Endpoint access mode Blacklist: only explicitly enabled endpoints are permitted.
Dim service = server.Security.UserNameAcl.AddEntry( _
"service", _
servicePassword, _
operationAccessMode:=OpcAccessControlMode.Whitelist, _
endpointAccessMode:=OpcAccessControlMode.Blacklist)
service.Enable(endpoint)Logon with tokens
For user identities with issued tokens, the IssuedTokenAcl validates Kerberos tickets only, and only if the server runs on .NET Framework. On .NET Standard and .NET (Core), the server rejects such logons.
Roles
Types usedOpcServerSecurityOpcServerSecurityRolesOpcAccessControlRoleOpcAccessControlEntryOpcPrincipal
OPC UA defines a set of standard roles to which the users of a server can belong. The Roles property of the OpcServerSecurity class provides these roles as instances of the OpcAccessControlRole class:
| Role | Meaning |
|---|---|
Anonymous | May browse and read non-security-related nodes in the Server object and all type nodes. |
Authenticated | May browse and read non-security-related nodes. |
Observer | May browse, read current and historical data and events, and subscribe to data and events. |
Operator | Like Observer, and may also write some current data and call some methods. |
Engineer | May browse, read and write configuration data, read historical data and events, call methods, and subscribe. |
Supervisor | May browse, read current and historical data and events, call methods, and subscribe. |
ConfigureAdmin | May change non-security-related configuration settings. |
SecurityAdmin | May change security-related settings, for example the certificate of the server (see Manage certificates with GDS). |
The server automatically assigns anonymous users to the Anonymous role and all users logged on through an access control list to the AuthenticatedUser role. You assign further roles to an access control entry with the Assign method:
var acl = server.Security.UserNameAcl;
var operator1 = acl.AddEntry("operator1", password: "password1");
operator1.Assign(server.Security.Roles.Operator);
var admin = acl.AddEntry("admin", password: "password2");
admin.Assign(server.Security.Roles.ConfigureAdmin, server.Security.Roles.SecurityAdmin);
acl.IsEnabled = true;Dim acl = server.Security.UserNameAcl
Dim operator1 = acl.AddEntry("operator1", password:="password1")
operator1.Assign(server.Security.Roles.Operator)
Dim admin = acl.AddEntry("admin", password:="password2")
admin.Assign(server.Security.Roles.ConfigureAdmin, server.Security.Roles.SecurityAdmin)
acl.IsEnabled = TrueThe Revoke method removes the assignment again:
admin.Revoke(server.Security.Roles.ConfigureAdmin);admin.Revoke(server.Security.Roles.ConfigureAdmin)To find out whether a principal belongs to a role, use the IsInRole method of the OpcPrincipal class or the Contains method of the respective role. The AccessControlRoles property of the OpcServerSecurity class returns the roles the server uses.
Access control modes
Types usedOpcAccessControlListOpcAccessControlEntryOpcAccessControlModeOpcRequestType
The access control mode of an entry (OpcAccessControlMode enumeration) determines how the server evaluates the operations allowed (Allow) and denied (Deny) for the entry:
- Whitelist (default of the access control lists of the SDK): All operations are allowed unless they were explicitly denied for the entry.
- Blacklist: All operations are denied unless they were explicitly allowed for the entry.
- Explicit: Only what was explicitly allowed or denied for the entry counts. The server denies only explicitly denied operations.
You set the mode when you add an entry:
var guest = acl.AddEntry(
"guest",
password: "password3",
operationAccessMode: OpcAccessControlMode.Blacklist);
// In blacklist mode only explicitly allowed operations are permitted.
guest.Allow(OpcRequestType.Browse, OpcRequestType.Read);Dim guest = acl.AddEntry( _
"guest", _
password:="password3", _
operationAccessMode:=OpcAccessControlMode.Blacklist)
'In blacklist mode only explicitly allowed operations are permitted.
guest.Allow(OpcRequestType.Browse, OpcRequestType.Read)Custom access control lists
Types usedOpcServerSecurityOpcAccessControlListOpcAccessControlTokenTypeOpcIdentityTokenOpcUserNameIdentityTokenOpcPrincipalOpcServerIdentity
If you do not want to maintain the users of a server in its code, but in an existing user management system, for example, implement a custom access control list. To do this, derive from the OpcAccessControlList class, set the kind of supported identities with the TokenType property, and implement the Authenticate method. If the method returns a principal, the user is logged on. If it returns null, the server checks the next active access control list. As with the access control lists of the SDK, assign logged-on users to the AuthenticatedUser role:
public class MyUserNameAcl : OpcAccessControlList
{
private readonly Dictionary<string, OpcPrincipal> principals
= new Dictionary<string, OpcPrincipal>();
public MyUserNameAcl(OpcServerBase owner)
: base(
owner,
operationAccessMode: OpcAccessControlMode.Whitelist,
endpointAccessMode: OpcAccessControlMode.Whitelist)
{
}
protected override OpcAccessControlTokenType TokenType
{
get { return OpcAccessControlTokenType.UserName; }
}
public override OpcPrincipal Authenticate(OpcIdentityToken identityToken)
{
var token = identityToken as OpcUserNameIdentityToken;
// Verify the user name and password, e.g. using an existing user management.
if (token == null || !UserStore.Verify(token.UserName, token.DecryptedPassword))
return null;
lock (this.principals) {
OpcPrincipal principal;
if (!this.principals.TryGetValue(token.UserName, out principal)) {
principal = new OpcPrincipal(new OpcServerIdentity(token.UserName));
this.AddEntry(principal);
// Authenticated users are expected to be a member of the AuthenticatedUser role.
this.Owner.Security.Roles.AuthenticatedUser.Assign(principal);
this.principals.Add(token.UserName, principal);
}
return principal;
}
}
}Public Class MyUserNameAcl
Inherits OpcAccessControlList
Private ReadOnly principals As New Dictionary(Of String, OpcPrincipal)()
Public Sub New(ByVal owner As OpcServerBase)
MyBase.New( _
owner, _
operationAccessMode:=OpcAccessControlMode.Whitelist, _
endpointAccessMode:=OpcAccessControlMode.Whitelist)
End Sub
Protected Overrides ReadOnly Property TokenType As OpcAccessControlTokenType
Get
Return OpcAccessControlTokenType.UserName
End Get
End Property
Public Overrides Function Authenticate(ByVal identityToken As OpcIdentityToken) As OpcPrincipal
Dim token = TryCast(identityToken, OpcUserNameIdentityToken)
'Verify the user name and password, e.g. using an existing user management.
If token Is Nothing OrElse Not UserStore.Verify(token.UserName, token.DecryptedPassword) Then
Return Nothing
End If
SyncLock Me.principals
Dim principal As OpcPrincipal = Nothing
If Not Me.principals.TryGetValue(token.UserName, principal) Then
principal = New OpcPrincipal(New OpcServerIdentity(token.UserName))
Me.AddEntry(principal)
'Authenticated users are expected to be a member of the AuthenticatedUser role.
Me.Owner.Security.Roles.AuthenticatedUser.Assign(principal)
Me.principals.Add(token.UserName, principal)
End If
Return principal
End SyncLock
End Function
End ClassThe AddAccessControlList method registers the custom access control list with the server and enables it. The RemoveAccessControlList method removes it again:
var customAcl = new MyUserNameAcl(server);
server.Security.AddAccessControlList(customAcl);
customAcl.IsEnabled = true;Dim customAcl = New MyUserNameAcl(server)
server.Security.AddAccessControlList(customAcl)
customAcl.IsEnabled = TrueAs with the access control lists of the SDK, enabling a custom access control list switches off anonymous access automatically (see Authenticate users).
Limit logon attempts
Types usedOpcServerOpcAuthenticationLimits
To protect against brute-force attacks on passwords, the server limits logon attempts through the AuthenticationLimits property. It counts the attempts per user and client IP address. If more than MaxFailedAttempts (5 by default) logon attempts fail within the DelayWindow time window (5 seconds by default), the server delays every further attempt. It first delays by Delay (1 second by default) and doubles the delay with each further failed attempt, up to MaxDelay (30 seconds by default). A successful logon resets the counter:
server.AuthenticationLimits.MaxFailedAttempts = 3; // Default: 5
server.AuthenticationLimits.DelayWindow = TimeSpan.FromSeconds(10); // Default: 5 seconds
server.AuthenticationLimits.Delay = TimeSpan.FromSeconds(2); // Default: 1 second
server.AuthenticationLimits.MaxDelay = TimeSpan.FromMinutes(1); // Default: 30 secondsserver.AuthenticationLimits.MaxFailedAttempts = 3 'Default: 5
server.AuthenticationLimits.DelayWindow = TimeSpan.FromSeconds(10) 'Default: 5 seconds
server.AuthenticationLimits.Delay = TimeSpan.FromSeconds(2) 'Default: 1 second
server.AuthenticationLimits.MaxDelay = TimeSpan.FromMinutes(1) 'Default: 30 secondsThe server also limits the number of concurrent logon attempts per identity (MaxConcurrentAttemptsPerIdentity, 3 by default) and in total (MaxConcurrentAttempts, 32 by default). It rejects attempts beyond these limits with the status BadUserAccessDenied. If the server should determine these limits from the processor cores and the available memory, enable the AutoDetermineLimits property:
// Default: false
server.AuthenticationLimits.AutoDetermineLimits = true;'Default: False
server.AuthenticationLimits.AutoDetermineLimits = TrueFor connections over the https scheme, the server has no information about the remote peer. The limit therefore does not apply there.
The SessionActivationFailed event reports rejected logons (see Audit events).
Protect against overload
Types usedOpcServerOpcOperationLimitsOpcTransportOpcRequestValidatingEventArgs
Clients can put load on a server with very large or very many requests. Three mechanisms limit this:
- Limits per request: By default, the server reports no limits to its clients on how many nodes they may read, write, browse, or subscribe to per request. With
OperationLimits.AutoDetermineLimits, the server determines suitable limits from the available system resources and reports them to its clients (see Data transfer limits). - Message limits: The limits under
Transportrestrict the size of messages, strings, and arrays. Increase them only as far as the application requires. - Reject requests: With the
RequestValidatingevent, the server can reject requests before processing them, for example all requests that change the structure of the address space (see Validate requests):
server.RequestValidating += HandleRequestValidating;
...
private static void HandleRequestValidating(
object sender,
OpcRequestValidatingEventArgs e)
{
// Clients must not change the structure of the address space.
if (e.RequestType == OpcRequestType.AddNodes
|| e.RequestType == OpcRequestType.DeleteNodes
|| e.RequestType == OpcRequestType.AddReferences
|| e.RequestType == OpcRequestType.DeleteReferences) {
e.Cancel = true;
}
}AddHandler server.RequestValidating, AddressOf HandleRequestValidating
...
Private Shared Sub HandleRequestValidating( _
ByVal sender As Object, _
ByVal e As OpcRequestValidatingEventArgs)
'Clients must not change the structure of the address space.
If e.RequestType = OpcRequestType.AddNodes _
OrElse e.RequestType = OpcRequestType.DeleteNodes _
OrElse e.RequestType = OpcRequestType.AddReferences _
OrElse e.RequestType = OpcRequestType.DeleteReferences Then
e.Cancel = True
End If
End SubManage certificates with GDS
A Global Discovery Server (GDS) can manage the application certificate and the trust list of the server remotely (GDS Push Management). The page Manage certificates with GDS describes the prerequisites, procedure, and limitations.
Audit events
Types usedOpcServerOpcServerSecurityOpcSessionOpcRequestValidatingEventArgsOpcSessionActivationFailedEventArgs
Audit events (according to OPC UA Part 4) record security-related actions. The server reports them only if the Security.IsAuditingEnabled property is true (false by default). You can also switch the property at runtime; it then takes effect at once:
server.Security.IsAuditingEnabled = true;server.Security.IsAuditingEnabled = TrueThe server reports the following audit events through the Server object:
| Event type | Raised |
|---|---|
| AuditOpenSecureChannelEventType | when a client opens a secure channel; also when the server rejects opening or renewing a secure channel |
| AuditChannelEventType | when a client closes a secure channel with a CloseSecureChannel request |
| AuditCreateSessionEventType | when a client creates a session |
| AuditActivateSessionEventType | when a client activates a session, that is, logs on |
The server reports the events about secure channels only for connections over opc.tcp. As defined by OPC UA, a client receives the audit events only if it is connected through an endpoint with the mode SignAndEncrypt or through HTTPS and its user belongs to the SecurityAdmin role (see Roles).
The server does not report further audit events, such as for writes, method calls, or certificate changes. An application cannot report its own audit events. The section Receive audit events describes how a client receives the events.
If your application needs a log of security-related actions, keep it yourself with the events of the server, for example SessionActivated for logons, SessionActivationFailed for rejected logons, and RequestValidating for writes and method calls (see Monitor requests):
server.SessionActivated += HandleSessionActivated;
server.RequestValidating += HandleCriticalRequest;
...
private static void HandleSessionActivated(object sender, OpcSessionEventArgs e)
{
Console.WriteLine(
"{0:u} session {1}: {2}",
DateTime.UtcNow,
e.Session.Name,
e.Session.UsedIdentity);
}
private static void HandleCriticalRequest(object sender, OpcRequestValidatingEventArgs e)
{
if (e.RequestType == OpcRequestType.Write || e.RequestType == OpcRequestType.Call) {
Console.WriteLine(
"{0:u} {1} in session {2}",
DateTime.UtcNow,
e.RequestType,
e.Context.SessionId);
}
}AddHandler server.SessionActivated, AddressOf HandleSessionActivated
AddHandler server.RequestValidating, AddressOf HandleCriticalRequest
...
Private Shared Sub HandleSessionActivated(ByVal sender As Object, ByVal e As OpcSessionEventArgs)
Console.WriteLine( _
"{0:u} session {1}: {2}", _
DateTime.UtcNow, _
e.Session.Name, _
e.Session.UsedIdentity)
End Sub
Private Shared Sub HandleCriticalRequest( _
ByVal sender As Object, _
ByVal e As OpcRequestValidatingEventArgs)
If e.RequestType = OpcRequestType.Write OrElse e.RequestType = OpcRequestType.Call Then
Console.WriteLine( _
"{0:u} {1} in session {2}", _
DateTime.UtcNow, _
e.RequestType, _
e.Context.SessionId)
End If
End SubSessionActivationFailed occurs for each rejected logon. The Status property states the reason, and the Identity property states the identity the client tried to log on with, without its password or token data. For a user name, it is an OpcUserNameIdentityTokenInfo with the name of the user (UserName); for a user certificate, it is an OpcCertificateIdentityTokenInfo with the certificate (Certificate):
server.SessionActivationFailed += HandleSessionActivationFailed;
...
private static void HandleSessionActivationFailed(
object sender,
OpcSessionActivationFailedEventArgs e)
{
var userName = e.Identity as OpcUserNameIdentityTokenInfo;
Console.WriteLine(
"{0:u} logon rejected ({1}): {2}",
DateTime.UtcNow,
e.Status.Code,
userName != null ? userName.UserName : "no user name");
}AddHandler server.SessionActivationFailed, AddressOf HandleSessionActivationFailed
...
Private Shared Sub HandleSessionActivationFailed( _
ByVal sender As Object, _
ByVal e As OpcSessionActivationFailedEventArgs)
Dim userName = TryCast(e.Identity, OpcUserNameIdentityTokenInfo)
Console.WriteLine( _
"{0:u} logon rejected ({1}): {2}", _
DateTime.UtcNow, _
e.Status.Code, _
If(userName IsNot Nothing, userName.UserName, "no user name"))
End SubHarden for production
Check the following points before you go to production:
- Offer encrypted endpoints only: add only policies with
SignAndEncryptand a current algorithm (see Secure endpoints and the network). - Restrict network interfaces: limit the
Bindingproperty to the addresses you need. - Reject unknown client certificates: disable
AutoAcceptUntrustedCertificatesand add client certificates to theTrustedPeerStore(see Validate and trust client certificates). - Validate certificates strictly: set
CertificateValidationRulestoRejectSHA1SignedandDisableBackwardsCompatibleValidation. - Manage your own certificate: use a certificate with an appropriate key length and lifetime, disable
AutoCreateCertificate, and place the certificate stores in a protected directory (see Protect your own certificate). - Switch off anonymous access and manage users: allow known users only, store passwords as hash values, and validate user certificates (see Authenticate users).
- Keep rights to a minimum: allow each user only the operations they need, and assign roles deliberately (see Roles and Access control modes).
- Limit logon attempts: adjust the
AuthenticationLimitsto the number of users (see Limit logon attempts). - Prevent overload: enable
OperationLimits.AutoDetermineLimits, do not increase the limits underTransportunnecessarily, and reject unwanted requests (see Protect against overload). - Enable GDS Push deliberately: assign the
SecurityAdminrole only to one identity with a certificate (see Manage certificates with GDS). - Log security-related actions: turn on
IsAuditingEnabledif needed, log logons, rejected logons, and critical requests yourself (see Audit events), and monitor the metrics of the server (see Diagnostics). - Protect the license key: do not store the key in plain text in publicly accessible files or repositories (see Negligent disclosure).
In summary, a server configured this way looks like this:
var server = new OpcServer("opc.tcp://192.168.0.10:4840/");
// Only encrypted endpoints with current algorithms (removes the implicit None endpoint).
server.Security.EndpointPolicies.Add(new OpcSecurityPolicy(
OpcSecurityMode.SignAndEncrypt,
OpcSecurityAlgorithm.Basic256Sha256));
server.Security.EndpointPolicies.Add(new OpcSecurityPolicy(
OpcSecurityMode.SignAndEncrypt,
OpcSecurityAlgorithm.Aes256_Sha256_RsaPss));
// Listen only on the network interface that clients use.
server.Binding.Addresses.Add(IPAddress.Parse("192.168.0.10"));
// Trust only known client certificates and check them strictly.
server.Security.AutoAcceptUntrustedCertificates = false;
server.Security.CertificateValidationRules
= OpcCertificateValidationRules.RejectSHA1Signed
| OpcCertificateValidationRules.DisableBackwardsCompatibleValidation;
// No anonymous access, only known users.
server.Security.UserNameAcl.AddEntry("operator", operatorPassword);
server.Security.UserNameAcl.IsEnabled = true;
server.Security.AnonymousAcl.IsEnabled = false;
// Report operation limits to the clients.
server.OperationLimits.AutoDetermineLimits = true;
// Use a managed certificate instead of a generated one.
server.CertificateStores.AutoCreateCertificate = false;
server.Certificate = OpcCertificateManager.LoadCertificate(certificatePath, certificatePassword);
server.Start();Dim server = New OpcServer("opc.tcp://192.168.0.10:4840/")
'Only encrypted endpoints with current algorithms (removes the implicit None endpoint).
server.Security.EndpointPolicies.Add(New OpcSecurityPolicy( _
OpcSecurityMode.SignAndEncrypt, _
OpcSecurityAlgorithm.Basic256Sha256))
server.Security.EndpointPolicies.Add(New OpcSecurityPolicy( _
OpcSecurityMode.SignAndEncrypt, _
OpcSecurityAlgorithm.Aes256_Sha256_RsaPss))
'Listen only on the network interface that clients use.
server.Binding.Addresses.Add(IPAddress.Parse("192.168.0.10"))
'Trust only known client certificates and check them strictly.
server.Security.AutoAcceptUntrustedCertificates = False
server.Security.CertificateValidationRules _
= OpcCertificateValidationRules.RejectSHA1Signed _
Or OpcCertificateValidationRules.DisableBackwardsCompatibleValidation
'No anonymous access, only known users.
server.Security.UserNameAcl.AddEntry("operator", operatorPassword)
server.Security.UserNameAcl.IsEnabled = True
server.Security.AnonymousAcl.IsEnabled = False
'Report operation limits to the clients.
server.OperationLimits.AutoDetermineLimits = True
'Use a managed certificate instead of a generated one.
server.CertificateStores.AutoCreateCertificate = False
server.Certificate = OpcCertificateManager.LoadCertificate(certificatePath, certificatePassword)
server.Start()
