Skip to content

How-to guide

Secure servers

Secure the server: endpoints, certificates, users, roles and access control lists, audit events, and a checklist for production.

This content was machine translated and may contain errors. Read in German

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

SettingDefault valueRecommendation for production
Security.EndpointPoliciesempty: the server offers only endpoints with the mode Noneonly policies with SignAndEncrypt and Basic256Sha256, Aes128_Sha256_RsaOaep, or Aes256_Sha256_RsaPss
Binding.Addressesempty: the server binds to all IPv4 and IPv6 addresses of the computer, even if its address is localhostrestrict to the network interfaces that clients use
Security.AutoAcceptUntrustedCertificatestrue: the server accepts unknown client certificatesfalse; add client certificates to the TrustedPeerStore in advance
Security.CertificateValidationRulesDefault: the server accepts certificates signed with SHA-1, and backward-compatible validation is activeRejectSHA1Signed and DisableBackwardsCompatibleValidation
Security.AnonymousAclactive as long as no other access control list is activedisable; allow known users only
Security.IsAuditingEnabledfalse: the server reports no audit eventstrue if needed (see Audit events)
CertificateStores.AutoCreateCertificatetrue: 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 certificateself-signed, key length 2048 bits, valid for 600 monthscertificate from your own certificate authority with a lifetime according to your policy
AuthenticationLimitsactive: after 5 failed attempts within 5 seconds, an increasing delay starting at 1 second, at most 30 secondsadjust to the number of users
OperationLimits.AutoDetermineLimitsfalse: the server reports no per-request limits to its clientstrue
TransportMaxMessageSize 10 MB, MaxByteStringLength 5 MB, MaxStringLength and MaxArrayLength 65535 eachincrease 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:

csharp
server.Security.AutoAcceptUntrustedCertificates = false;
vbnet
server.Security.AutoAcceptUntrustedCertificates = False

Validate certificates yourself: Once you have disabled the default acceptance of all certificates, you should provide custom validation of the certificates:

csharp
server.CertificateValidationFailed += HandleCertificateValidationFailed;
...
private void HandleCertificateValidationFailed(object sender, OpcCertificateValidationFailedEventArgs e)
{
    if (e.Certificate.Thumbprint == "...")
        e.Accept = true;
}
vbnet
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 Sub

Trust 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:

csharp
// 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);
vbnet
' 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 If

Stricter rules for certificate validation: The CertificateValidationRules property sets them. By default, the server also accepts certificates signed with SHA-1:

csharp
server.Security.CertificateValidationRules
        = OpcCertificateValidationRules.RejectSHA1Signed
        | OpcCertificateValidationRules.DisableBackwardsCompatibleValidation;
vbnet
server.Security.CertificateValidationRules _
        = OpcCertificateValidationRules.RejectSHA1Signed _
        Or OpcCertificateValidationRules.DisableBackwardsCompatibleValidation

Protect 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 the OpcCertificateSettings class. 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 from OpcServerInfo.CompanyName (property Organization). 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 CertificateRequested event. Up to version 2.58, the following applies: If no handler provides a certificate and the AutoCreateCertificate property is active, the server deletes the previous certificate from the ApplicationStore and 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. The Reason property of the event data then states the reason: Invalid, UseNotAllowed, Revoked, TimeInvalid, IssuerUseNotAllowed, IssuerRevoked, or IssuerTimeInvalid. For an expired certificate, for example, it is TimeInvalid. The AutoCreateCertificate property of the event data is false in 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 the AlternativeCertificate property at startup. The certificate must contain its private key. Provide a certificate yourself through the CertificateRequested event and disable automatic creation.
  • Protect the private key: The ApplicationStore contains 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 .pfx file.

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:

csharp
server.Security.AnonymousAcl.IsEnabled = false;
vbnet
server.Security.AnonymousAcl.IsEnabled = False

Configure the access control list for user names and passwords:

csharp
var acl = server.Security.UserNameAcl;

acl.AddEntry("username1", "password1");
acl.AddEntry("username2", "password2");
acl.AddEntry("username3", "password3");
...
acl.IsEnabled = true;
vbnet
Dim acl = server.Security.UserNameAcl

acl.AddEntry("username1", "password1")
acl.AddEntry("username2", "password2")
acl.AddEntry("username3", "password3")
'...
acl.IsEnabled = True

Configure the access control list for certificates:

csharp
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;
vbnet
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 = True

If 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:

csharp
var user1 = acl.AddEntry("username1", "password1");
vbnet
Dim user1 = acl.AddEntry("username1", "password1")

2. Deny the access control entry two rights:

csharp
user1.Deny(OpcRequestType.Write);
user1.Deny(OpcRequestType.HistoryUpdate);
vbnet
user1.Deny(OpcRequestType.Write)
user1.Deny(OpcRequestType.HistoryUpdate)

3. Allow a previously denied right again:

csharp
user1.Allow(OpcRequestType.HistoryUpdate);
vbnet
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:

csharp
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);
    }
}
vbnet
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 Class

To add the identity:

csharp
// 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;
vbnet
'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 = True

Validate user certificates ​

Types usedOpcCertificateAcl

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:

csharp
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.");
        }
    }
}
vbnet
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 Class
csharp
server.Security.CertificateAcl.CertificateValidator = new UserCertificateValidator();
server.Security.CertificateAcl.AddEntry(userCertificate);
server.Security.CertificateAcl.IsEnabled = true;
vbnet
server.Security.CertificateAcl.CertificateValidator = New UserCertificateValidator()
server.Security.CertificateAcl.AddEntry(userCertificate)
server.Security.CertificateAcl.IsEnabled = True

Restrict 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:

csharp
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);
vbnet
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:

RoleMeaning
AnonymousMay browse and read non-security-related nodes in the Server object and all type nodes.
AuthenticatedUserMay browse and read non-security-related nodes.
ObserverMay browse, read current and historical data and events, and subscribe to data and events.
OperatorLike Observer, and may also write some current data and call some methods.
EngineerMay browse, read and write configuration data, read historical data and events, call methods, and subscribe.
SupervisorMay browse, read current and historical data and events, call methods, and subscribe.
ConfigureAdminMay change non-security-related configuration settings.
SecurityAdminMay 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:

csharp
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;
vbnet
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 = True

The Revoke method removes the assignment again:

csharp
admin.Revoke(server.Security.Roles.ConfigureAdmin);
vbnet
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:

csharp
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);
vbnet
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:

csharp
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;
        }
    }
}
vbnet
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 Class

The AddAccessControlList method registers the custom access control list with the server and enables it. The RemoveAccessControlList method removes it again:

csharp
var customAcl = new MyUserNameAcl(server);
server.Security.AddAccessControlList(customAcl);
 
customAcl.IsEnabled = true;
vbnet
Dim customAcl = New MyUserNameAcl(server)
server.Security.AddAccessControlList(customAcl)

customAcl.IsEnabled = True

As 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:

csharp
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 seconds
vbnet
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 seconds

The 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:

csharp
// Default: false
server.AuthenticationLimits.AutoDetermineLimits = true;
vbnet
'Default: False
server.AuthenticationLimits.AutoDetermineLimits = True

For 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 Transport restrict the size of messages, strings, and arrays. Increase them only as far as the application requires.
  • Reject requests: With the RequestValidating event, the server can reject requests before processing them, for example all requests that change the structure of the address space (see Validate requests):
csharp
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;
    }
}
vbnet
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 Sub

Manage 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:

csharp
server.Security.IsAuditingEnabled = true;
vbnet
server.Security.IsAuditingEnabled = True

The server reports the following audit events through the Server object:

Event typeRaised
AuditOpenSecureChannelEventTypewhen a client opens a secure channel; also when the server rejects opening or renewing a secure channel
AuditChannelEventTypewhen a client closes a secure channel with a CloseSecureChannel request
AuditCreateSessionEventTypewhen a client creates a session
AuditActivateSessionEventTypewhen 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):

csharp
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);
    }
}
vbnet
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 Sub

SessionActivationFailed 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):

csharp
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");
}
vbnet
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 Sub

Harden for production ​

Check the following points before you go to production:

  1. Offer encrypted endpoints only: add only policies with SignAndEncrypt and a current algorithm (see Secure endpoints and the network).
  2. Restrict network interfaces: limit the Binding property to the addresses you need.
  3. Reject unknown client certificates: disable AutoAcceptUntrustedCertificates and add client certificates to the TrustedPeerStore (see Validate and trust client certificates).
  4. Validate certificates strictly: set CertificateValidationRules to RejectSHA1Signed and DisableBackwardsCompatibleValidation.
  5. 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).
  6. Switch off anonymous access and manage users: allow known users only, store passwords as hash values, and validate user certificates (see Authenticate users).
  7. Keep rights to a minimum: allow each user only the operations they need, and assign roles deliberately (see Roles and Access control modes).
  8. Limit logon attempts: adjust the AuthenticationLimits to the number of users (see Limit logon attempts).
  9. Prevent overload: enable OperationLimits.AutoDetermineLimits, do not increase the limits under Transport unnecessarily, and reject unwanted requests (see Protect against overload).
  10. Enable GDS Push deliberately: assign the SecurityAdmin role only to one identity with a certificate (see Manage certificates with GDS).
  11. Log security-related actions: turn on IsAuditingEnabled if needed, log logons, rejected logons, and critical requests yourself (see Audit events), and monitor the metrics of the server (see Diagnostics).
  12. 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:

csharp
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();
vbnet
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()