Anleitung
Server absichern
Den Server absichern: Endpunkte, Zertifikate, Benutzer, Rollen und Zugriffskontrolllisten, Audit-Ereignisse und eine Checkliste für den Betrieb.
Anleitung
Server absichern
Diese Seite bündelt alle Mechanismen, mit denen ein Server sich und seine Daten schützt. Die Standardwerte des OPC UA .NET SDK sind auf einen einfachen Einstieg ausgelegt und für den Produktivbetrieb nicht geeignet. Welche Einstellungen Sie dafür ändern sollten, fasst der Abschnitt Härtung für den Produktivbetrieb zusammen. Die Grundlagen beschreiben die Seiten Zertifikate verstehen und Benutzer und Rollen.
Standardwerte im Überblick
Beteiligte TypenOpcServerOpcServerSecurityOpcServerBindingOpcCertificateStoresOpcAuthenticationLimitsOpcOperationLimits
| Einstellung | Standardwert | Empfehlung für den Produktivbetrieb |
|---|---|---|
Security. | leer: Der Server bietet nur Endpunkte mit dem Modus None an | ausschließlich Policies mit SignAndEncrypt und Basic256Sha256, Aes128_Sha256_Rsa oder Aes256_Sha256_Rsa |
Binding. | leer: Der Server bindet an alle IPv4- und IPv6-Adressen des Rechners, auch wenn seine Adresse localhost lautet | auf die Netzwerkschnittstellen beschränken, über die Clients zugreifen |
Security. | true: Der Server nimmt unbekannte Clientzertifikate an | false; Clientzertifikate vorab in den TrustedPeerStore aufnehmen |
Security. | Default: Der Server nimmt mit SHA-1 signierte Zertifikate an, die rückwärtskompatible Prüfung ist aktiv | RejectSHA1Signed und Disable |
Security. | aktiv, solange keine andere Zugriffskontrollliste aktiv ist | deaktivieren; nur bekannte Benutzer zulassen |
Security. | false: Der Server meldet keine Audit-Ereignisse | nach Bedarf true (siehe Audit-Ereignisse) |
Certificate | true: Fehlt ein Zertifikat, erzeugt der Server ein neues (bis Version 2.58 auch, wenn das vorhandene ungültig ist) | false und ein verwaltetes Zertifikat verwenden |
| Automatisch erzeugtes Zertifikat | selbstsigniert, Schlüssellänge 2048 Bit, gültig für 600 Monate | Zertifikat einer eigenen Zertifizierungsstelle mit einer Laufzeit nach Ihrer Richtlinie |
Authentication | aktiv: nach 5 Fehlversuchen innerhalb von 5 Sekunden wachsende Verzögerung ab 1 Sekunde, höchstens 30 Sekunden | an die Zahl der Benutzer anpassen |
OperationLimits. | false: Der Server meldet seinen Clients keine Grenzen je Anfrage | true |
Transport | MaxMessageSize 10 MB, Max 5 MB, MaxStringLength und MaxArrayLength je 65535 | nur so weit erhöhen, wie die Anwendung es braucht |
Unsichere Standardwerte
Mit den Standardwerten ist ein Server über alle Netzwerkschnittstellen des Rechners erreichbar, kommuniziert unverschlüsselt, lässt anonyme Benutzer zu und nimmt das Zertifikat jedes ihm unbekannten Clients an. Wer den Rechner über das Netzwerk erreicht, kann damit Daten lesen, schreiben und Methoden aufrufen. Ändern Sie diese Einstellungen vor dem Produktivbetrieb.
Endpunkte und Netzwerk absichern
Beteiligte TypenOpcServerOpcServerSecurityOpcSecurityPolicyOpcServerBinding
Nur verschlüsselte Endpunkte anbieten: Solange die Eigenschaft EndpointPolicies leer ist, bietet der Server ausschließlich Endpunkte mit dem Modus None an. Über diese Endpunkte überträgt er Daten weder signiert noch verschlüsselt. Sobald eine Policy eingetragen ist, entfällt der Endpunkt mit dem Modus None. Tragen Sie deshalb nur Policies mit dem Modus SignAndEncrypt ein und geben Sie den Algorithmus ausdrücklich an. Ohne Angabe (OpcSecurityAlgorithm.Auto) wählt das SDK Basic256Sha256. Wie der Server Adressen und Policies zu Endpunkten kombiniert, beschreibt der Abschnitt Endpunkte.
TLS-Versionen bei HTTPS beachten: Ab Version 2.59 überlässt der Server bei Endpunkten mit dem Schema https die Wahl der TLS-Versionen dem Betriebssystem. Bis Version 2.58 waren TLS 1.0, 1.1 und 1.2 fest eingestellt. Clients, die nur TLS 1.0 oder 1.1 unterstützen, können sich deshalb über HTTPS möglicherweise nicht mehr verbinden.
Nur an die benötigten Netzwerkschnittstellen binden: Die Adresse des Servers (zum Beispiel opc.tcp://localhost:4840/) bestimmt die Endpunkte, die der Server seinen Clients meldet. Sie bestimmt nicht die Netzwerkschnittstellen, an die er seine Ports bindet. Ohne weitere Angabe bindet der Server an alle IPv4- und IPv6-Adressen des Rechners. Damit ist er auch dann aus dem Netzwerk erreichbar, wenn seine Adresse localhost lautet. Beschränken Sie die Bindung über die Eigenschaft Binding auf die benötigten Adressen (siehe An Netzwerkschnittstellen binden). Die standardmäßig aktive exklusive Bindung (UseExclusiveAddress) verhindert zudem, dass ein anderer Prozess denselben Port belegt.
Clientzertifikate prüfen und vertrauen
Beteiligte TypenOpcServerOpcServerSecurityOpcCertificateValidationFailedEventArgsOpcCertificateValidationRulesOpcCertificateStoresOpcCertificateStoreInfo
Ein Client sendet beim Verbindungsaufbau sein Zertifikat zur Authentifizierung an den Server. Anhand des Clientzertifikats entscheidet der Server, ob er die Verbindung mit dem Client zulässt und ihm damit vertraut.
Nur vertrauenswürdige Zertifikate akzeptieren: Wenn der Server nur vertrauenswürdige Zertifikate akzeptieren soll, deaktivieren Sie die standardmäßige Akzeptanz aller unbekannten Zertifikate. Abgelaufene, gesperrte oder anderweitig ungültige Zertifikate nimmt der Server auch ohne diese Einstellung nicht automatisch an:
server.Security.AutoAcceptUntrustedCertificates = false;server.Security.AutoAcceptUntrustedCertificates = FalseZertifikate selbst prüfen: Sobald Sie die standardmäßige Akzeptanz aller Zertifikate deaktiviert haben, sollten Sie eine benutzerdefinierte Prüfung der Zertifikate vorsehen:
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 SubZertifikat manuell vertrauen: Wenn der Server ein Clientzertifikat als nicht vertrauenswürdig eingestuft hat, können Sie es manuell als vertrauenswürdig deklarieren. Speichern Sie es dazu im 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 IfStrengere Regeln für die Prüfung von Zertifikaten legt die Eigenschaft CertificateValidationRules fest. Standardmäßig akzeptiert der Server auch Zertifikate, die mit SHA-1 signiert sind:
server.Security.CertificateValidationRules
= OpcCertificateValidationRules.RejectSHA1Signed
| OpcCertificateValidationRules.DisableBackwardsCompatibleValidation;server.Security.CertificateValidationRules _
= OpcCertificateValidationRules.RejectSHA1Signed _
Or OpcCertificateValidationRules.DisableBackwardsCompatibleValidationEigenes Zertifikat schützen
Beteiligte TypenOpcServerOpcCertificateManagerOpcCertificateSettingsOpcCertificateStoresOpcCertificateRequestedEventArgs
Wie ein Zertifikat geladen, erzeugt und gespeichert wird, beschreibt der Abschnitt Anwendungszertifikat. Für die Sicherheit sind dabei die folgenden Punkte wichtig:
- Eigenschaften des Zertifikats: Ein vom SDK erzeugtes Zertifikat ist selbstsigniert, verwendet einen Schlüssel mit 2048 Bit und ist 600 Monate gültig. Schlüssellänge (
KeySize), Gültigkeit in Monaten (Lifetime) und die Domänen, unter denen der Server erreichbar ist (DomainNames), legen Sie über die KlasseOpcCertificateSettingsfest. Beim Server ergänzt das SDK die Domänen um den vollqualifizierten Namen des Rechners (FQDN), sofern er sich per DNS auflösen lässt. So nehmen Clients, die sich über diesen Namen verbinden, das Zertifikat an. Als Organisation (O) im Antragsteller (engl. subject) trägt das SDK den Hersteller ausOpcServerInfo.CompanyNameein (EigenschaftOrganization). Ein bereits vorhandenes Zertifikat ändert es nicht. Für den Produktivbetrieb empfiehlt sich ein Zertifikat, das die Zertifizierungsstelle (engl. certificate authority) Ihres Unternehmens ausgestellt hat. Clients müssen dann nur der Zertifizierungsstelle vertrauen. - Kein Zertifikat stillschweigend ersetzen: Wenn der Server beim Start kein gültiges Zertifikat findet, zum Beispiel weil es abgelaufen ist, löst er das Ereignis
CertificateRequestedaus. Bis Version 2.58 gilt: Wenn kein Handler ein Zertifikat liefert und die EigenschaftAutoCreateCertificateaktiv ist, löscht er das bisherige Zertifikat aus demApplicationStoreund erzeugt ein neues, selbstsigniertes Zertifikat. Clients, die dem bisherigen Zertifikat vertraut haben, lehnen den Server danach ab. Ab Version 2.59 ersetzt der Server ein vorhandenes, aber ungültiges Zertifikat nie automatisch. Die EigenschaftReasonder Ereignisdaten nennt dann den Grund:Invalid,UseNotAllowed,Revoked,TimeInvalid,IssuerUseNotAllowed,IssuerRevokedoderIssuerTimeInvalid. Für ein abgelaufenes Zertifikat lautet er zum BeispielTimeInvalid. Die EigenschaftAutoCreateCertificateder Ereignisdaten ist dabeifalse. Wenn der Handler kein anderes Zertifikat bereitstellt, verwendet der Server weiter das vorhandene Zertifikat. Ein Zertifikat, das der Handler über die EigenschaftAlternativeCertificatebereitstellt, verwendet der Server ab Version 2.59 beim Start. Es muss seinen privaten Schlüssel enthalten. Stellen Sie über das EreignisCertificateRequestedselbst ein Zertifikat bereit und deaktivieren Sie die automatische Erzeugung. - Den privaten Schlüssel schützen: Der
ApplicationStoreenthält das Zertifikat samt privatem Schlüssel. Das SDK legt für die Dateien der Zertifikatspeicher keine Zugriffsrechte fest. Legen Sie die Zertifikatspeicher in einem Verzeichnis ab, auf das nur das Benutzerkonto des Servers zugreifen darf. Laden Sie ein vorhandenes Zertifikat aus einer durch ein Passwort geschützten.pfx-Datei.
Benutzer anmelden
Beteiligte TypenOpcServerOpcUserIdentityOpcServerIdentityOpcCertificateIdentityOpcServerSecurityOpcAccessControlListOpcAnonymousAclOpcUserNameAclOpcCertificateAclOpcAccessControlEntryOpcOperationTypeOpcRequestTypeOpcAccessControlMode
Ein Server erlaubt standardmäßig auch Zugriffe ohne Angabe einer konkreten Benutzeridentität. Diese Art der Benutzerauthentifizierung heißt anonyme Authentifizierung. Wenn ein Client dagegen eine Benutzeridentität angibt, muss der Server diese Identität kennen. Nur dann lässt er den Zugriff mit dieser Identität zu. Wenn sich Benutzer zum Beispiel mit einem Benutzername-Passwort-Paar oder einem Zertifikat ausweisen sollen, konfigurieren und aktivieren Sie die passenden Zugriffskontrolllisten (engl. Access Control List, ACL). Zur Konfiguration der Zugriffskontrolllisten gehört die Konfiguration der Zugriffskontrolleinträge (engl. Access Control Entries, ACE). Jeder Eintrag steht für ein Prinzipal mit einer bestimmten Identität, also Benutzername und Passwort oder ein Zertifikat. Die Einträge tragen Sie in die Listen ein.
Die Zugriffskontrollliste für anonyme Benutzer ist nur so lange aktiv, wie keine andere Zugriffskontrollliste aktiv ist. Wenn Sie also zum Beispiel die Zugriffskontrollliste für Benutzername-Passwort-Paare aktivieren, ist der anonyme Zugriff automatisch abgeschaltet. Wenn er dennoch möglich sein soll, setzen Sie die Eigenschaft IsEnabled der AnonymousAcl ausdrücklich auf true. Umgekehrt schaltet das folgende Beispiel den anonymen Zugriff ausdrücklich ab:
Anonyme Zugriffskontrollliste deaktivieren:
server.Security.AnonymousAcl.IsEnabled = false;server.Security.AnonymousAcl.IsEnabled = FalseZugriffskontrollliste für Benutzername-Passwort-Paare konfigurieren:
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 = TrueZugriffskontrollliste für Zertifikate konfigurieren:
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 = TrueWenn über die Eigenschaft CertificateValidator der CertificateAcl ein eigener X509CertificateValidator festgelegt ist, prüft der Server das Zertifikat einer Benutzeridentität zusätzlich, bevor er den Fingerabdruck (engl. thumbprint) vergleicht.
Alle Zugriffskontrolllisten, die das SDK bereitstellt, verwenden den Zugriffskontrollmodus (engl. Access Control Mode) Whitelist. In diesem Modus hat jeder Eintrag allein dadurch, dass er definiert ist, Zugriff auf alle Arten (engl. Types) von Anfragen (engl. Requests). Das gilt auch, wenn ihm der Zugriff dafür nicht ausdrücklich erteilt (engl. allow) wurde. Entziehen (engl. deny) Sie den Einträgen deshalb alle nicht erlaubten Operationen. Die erlaubten und verbotenen Operationen legen Sie direkt auf dem Eintrag fest, den die Zugriffskontrollliste nach dem Eintragen bereitstellt. Andere Modi, Rollen und eigene Zugriffskontrolllisten beschreiben die Abschnitte Zugriffskontrollmodi, Rollen und Eigene Zugriffskontrolllisten.
1. Einen Zugriffskontrolleintrag merken:
var user1 = acl.AddEntry("username1", "password1");Dim user1 = acl.AddEntry("username1", "password1")2. Dem Zugriffskontrolleintrag zwei Rechte entziehen:
user1.Deny(OpcRequestType.Write);
user1.Deny(OpcRequestType.HistoryUpdate);user1.Deny(OpcRequestType.Write)
user1.Deny(OpcRequestType.HistoryUpdate)3. Ein zuvor entzogenes Recht wieder erteilen:
user1.Allow(OpcRequestType.HistoryUpdate);user1.Allow(OpcRequestType.HistoryUpdate)Passwörter geschützt ablegen
Beteiligte TypenOpcServerIdentityOpcUserNameAcl
Wenn Sie einen Benutzer mit einem Passwort als Zeichenkette eintragen (AddEntry(userName, password)), hält der Server das Passwort im Klartext im Speicher. Wenn die Passwörter Ihrer Benutzer bereits als Hashwerte vorliegen, leiten Sie von der Klasse OpcServerIdentity ab. Der Konstruktor übernimmt den gespeicherten Hashwert als Byte-Array. Die überschriebene Methode TransformPassword berechnet beim Anmelden aus dem übermittelten Passwort den Hashwert nach demselben Verfahren. Der Server vergleicht die beiden Werte in konstanter Zeit. Das folgende Beispiel verwendet PBKDF2 mit einem Salt je Benutzer:
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 ClassSo tragen Sie die Identität ein:
// 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 = TrueBenutzerzertifikate prüfen
Beteiligte TypenOpcCertificateAcl
Wenn sich ein Benutzer mit einem Zertifikat anmeldet, vergleicht der Server dessen Fingerabdruck mit den Einträgen der CertificateAcl. Wenn der Server das Zertifikat vorher prüfen soll, zum Beispiel gegen die Zertifizierungsstelle Ihres Unternehmens und auf eine Sperrung, legen Sie über die Eigenschaft CertificateValidator einen eigenen X509CertificateValidator fest (Namespace System.IdentityModel.Selectors). Wenn der Validator das Zertifikat mit einer Ausnahme ablehnt, verweigert der Server die Anmeldung:
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 = TrueBenutzer auf Endpunkte beschränken
Beteiligte TypenOpcAccessControlEntryOpcEndpointIdentityOpcAccessControlMode
Neben den Operationen kann ein Zugriffskontrolleintrag auch die Endpunkte festlegen, über die sich ein Benutzer anmelden darf. Wie der Server erteilte (Enable) und entzogene (Disable) Endpunkte auswertet, bestimmt der Zugriffskontrollmodus für Endpunkte. Sie geben ihn beim Hinzufügen eines Eintrags als zweiten Modus an. Im Modus Blacklist darf sich der Benutzer nur über ausdrücklich erteilte Endpunkte anmelden. Über alle anderen Endpunkte verweigert der Server die Anmeldung mit dem Status BadUserAccessDenied. So lässt sich zum Beispiel ein Dienstkonto auf einen Endpunkt mit dem Modus SignAndEncrypt beschränken. Adresse und Policy der OpcEndpointIdentity müssen dabei dem Endpunkt entsprechen, über den sich der Client verbindet:
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)Anmeldung mit Token
Für Benutzeridentitäten mit ausgestellten Token (engl. issued token) prüft die IssuedTokenAcl ausschließlich Kerberos-Tickets, und das nur, wenn der Server unter .NET Framework läuft. Unter .NET Standard und .NET (Core) weist der Server solche Anmeldungen ab.
Rollen
Beteiligte TypenOpcServerSecurityOpcServerSecurityRolesOpcAccessControlRoleOpcAccessControlEntryOpcPrincipal
OPC UA definiert eine Reihe von Standardrollen, denen die Benutzer eines Servers angehören können. Die Eigenschaft Roles der Klasse OpcServerSecurity stellt diese Rollen als Instanzen der Klasse OpcAccessControlRole bereit:
| Rolle | Bedeutung |
|---|---|
Anonymous | Darf nicht sicherheitsrelevante Nodes im Server-Objekt und alle Typ-Nodes browsen und lesen. |
Authenticated | Darf nicht sicherheitsrelevante Nodes browsen und lesen. |
Observer | Darf browsen, aktuelle und historische Daten und Ereignisse lesen und Daten und Ereignisse abonnieren. |
Operator | Wie Observer, darf zusätzlich einige aktuelle Daten schreiben und einige Methoden aufrufen. |
Engineer | Darf browsen, Konfigurationsdaten lesen und schreiben, historische Daten und Ereignisse lesen, Methoden aufrufen und abonnieren. |
Supervisor | Darf browsen, aktuelle und historische Daten und Ereignisse lesen, Methoden aufrufen und abonnieren. |
ConfigureAdmin | Darf nicht sicherheitsrelevante Einstellungen der Konfiguration ändern. |
SecurityAdmin | Darf sicherheitsrelevante Einstellungen ändern, zum Beispiel das Zertifikat des Servers (siehe Zertifikate per GDS verwalten). |
Anonyme Benutzer ordnet der Server automatisch der Rolle Anonymous zu, alle über eine Zugriffskontrollliste angemeldeten Benutzer der Rolle AuthenticatedUser. Weitere Rollen ordnen Sie einem Zugriffskontrolleintrag über die Methode Assign zu:
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 = TrueÜber die Methode Revoke heben Sie die Zuordnung wieder auf:
admin.Revoke(server.Security.Roles.ConfigureAdmin);admin.Revoke(server.Security.Roles.ConfigureAdmin)Ob ein Prinzipal einer Rolle angehört, ermittelt die Methode IsInRole der Klasse OpcPrincipal beziehungsweise die Methode Contains der jeweiligen Rolle. Die Rollen, die der Server verwendet, liefert die Eigenschaft AccessControlRoles der Klasse OpcServerSecurity.
Zugriffskontrollmodi
Beteiligte TypenOpcAccessControlListOpcAccessControlEntryOpcAccessControlModeOpcRequestType
Wie der Server die einem Zugriffskontrolleintrag erteilten (Allow) und entzogenen (Deny) Operationen auswertet, legt der Zugriffskontrollmodus des Eintrags fest (Enumeration OpcAccessControlMode):
- Whitelist (Standard der Zugriffskontrolllisten des SDK): Alle Operationen sind erlaubt, solange sie dem Eintrag nicht ausdrücklich entzogen wurden.
- Blacklist: Alle Operationen sind verweigert, solange sie dem Eintrag nicht ausdrücklich erteilt wurden.
- Explicit: Es zählt nur, was dem Eintrag ausdrücklich erteilt oder entzogen wurde. Der Server verweigert dabei ausschließlich ausdrücklich entzogene Operationen.
Den Modus legen Sie beim Hinzufügen eines Eintrags fest:
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)Eigene Zugriffskontrolllisten
Beteiligte TypenOpcServerSecurityOpcAccessControlListOpcAccessControlTokenTypeOpcIdentityTokenOpcUserNameIdentityTokenOpcPrincipalOpcServerIdentity
Wenn Sie die Benutzer eines Servers nicht im Code des Servers pflegen möchten, sondern zum Beispiel in einer bestehenden Benutzerverwaltung, implementieren Sie eine eigene Zugriffskontrollliste. Leiten Sie dazu von der Klasse OpcAccessControlList ab, legen Sie über die Eigenschaft TokenType die Art der unterstützten Identitäten fest und implementieren Sie die Methode Authenticate. Wenn die Methode ein Prinzipal liefert, gilt der Benutzer als angemeldet. Wenn sie null liefert, prüft der Server die nächste aktive Zugriffskontrollliste. Ordnen Sie angemeldete Benutzer wie bei den Zugriffskontrolllisten des SDK der Rolle AuthenticatedUser zu:
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 ClassÜber die Methode AddAccessControlList machen Sie die eigene Zugriffskontrollliste dem Server bekannt und aktivieren sie. Über die Methode RemoveAccessControlList entfernen Sie sie wieder:
var customAcl = new MyUserNameAcl(server);
server.Security.AddAccessControlList(customAcl);
customAcl.IsEnabled = true;Dim customAcl = New MyUserNameAcl(server)
server.Security.AddAccessControlList(customAcl)
customAcl.IsEnabled = TrueWie bei den Zugriffskontrolllisten des SDK schaltet die Aktivierung einer eigenen Zugriffskontrollliste den anonymen Zugriff automatisch ab (siehe Benutzer anmelden).
Anmeldeversuche begrenzen
Beteiligte TypenOpcServerOpcAuthenticationLimits
Zum Schutz vor dem Durchprobieren von Passwörtern (engl. brute force) begrenzt der Server die Anmeldeversuche über die Eigenschaft AuthenticationLimits. Er zählt die Versuche je Benutzer und IP-Adresse des Clients. Wenn innerhalb des Zeitfensters DelayWindow (standardmäßig 5 Sekunden) mehr als MaxFailedAttempts (standardmäßig 5) Anmeldeversuche fehlschlagen, verzögert der Server jeden weiteren Versuch. Er verzögert zunächst um Delay (standardmäßig 1 Sekunde) und bei weiteren Fehlversuchen jeweils doppelt so lange, höchstens jedoch um MaxDelay (standardmäßig 30 Sekunden). Eine erfolgreiche Anmeldung setzt den Zähler zurück:
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 secondsZudem begrenzt der Server die Zahl gleichzeitiger Anmeldeversuche je Identität (MaxConcurrentAttemptsPerIdentity, standardmäßig 3) und insgesamt (MaxConcurrentAttempts, standardmäßig 32). Darüber hinausgehende Versuche lehnt er mit dem Status BadUserAccessDenied ab. Wenn der Server diese Grenzen anhand der Prozessorkerne und des verfügbaren Speichers ermitteln soll, aktivieren Sie die Eigenschaft AutoDetermineLimits:
// Default: false
server.AuthenticationLimits.AutoDetermineLimits = true;'Default: False
server.AuthenticationLimits.AutoDetermineLimits = TrueFür Verbindungen über das Schema https stehen dem Server keine Informationen zur Gegenstelle zur Verfügung. Die Begrenzung gilt dort deshalb nicht.
Abgelehnte Anmeldungen meldet das Ereignis SessionActivationFailed (siehe Audit-Ereignisse).
Schutz vor Überlastung
Beteiligte TypenOpcServerOpcOperationLimitsOpcTransportOpcRequestValidatingEventArgs
Clients können einen Server mit sehr großen oder sehr vielen Anfragen belasten. Drei Mechanismen begrenzen das:
- Grenzen je Anfrage: Standardmäßig meldet der Server seinen Clients keine Grenzen dafür, wie viele Nodes sie je Anfrage lesen, schreiben, browsen oder abonnieren dürfen. Mit
OperationLimits.AutoDetermineLimitsermittelt der Server anhand der verfügbaren Systemressourcen geeignete Grenzen und meldet sie seinen Clients (siehe Grenzen der Datenübertragung). - Grenzen der Nachrichten: Die Grenzen unter
Transportbegrenzen die Größe von Nachrichten, Zeichenketten und Arrays. Erhöhen Sie sie nur so weit, wie es die Anwendung erfordert. - Anfragen ablehnen: Über das Ereignis
RequestValidatingkann der Server Anfragen vor der Verarbeitung ablehnen, zum Beispiel alle Anfragen, die die Struktur des Adressraums verändern (siehe Anfragen validieren):
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 SubZertifikate per GDS verwalten
Ein Global Discovery Server (GDS) kann das Anwendungszertifikat und die vertrauenswürdigen Zertifikate des Servers aus der Ferne verwalten (GDS Push Management). Voraussetzungen, Ablauf und Einschränkungen beschreibt die Seite Zertifikate per GDS verwalten.
Audit-Ereignisse
Beteiligte TypenOpcServerOpcServerSecurityOpcSessionOpcRequestValidatingEventArgsOpcSessionActivationFailedEventArgs
Audit-Ereignisse (gemäß OPC UA Teil 4) protokollieren sicherheitsrelevante Aktionen. Der Server meldet sie nur, wenn die Eigenschaft Security.IsAuditingEnabled den Wert true hat (standardmäßig false). Die Eigenschaft lässt sich auch im laufenden Betrieb umschalten und wirkt dann sofort:
server.Security.IsAuditingEnabled = true;server.Security.IsAuditingEnabled = TrueDer Server meldet die folgenden Audit-Ereignisse über das Server-Objekt:
| Ereignistyp | Ausgelöst |
|---|---|
| AuditOpenSecureChannelEventType | wenn ein Client einen sicheren Kanal öffnet; außerdem, wenn der Server das Öffnen oder Erneuern eines sicheren Kanals ablehnt |
| AuditChannelEventType | wenn ein Client einen sicheren Kanal mit einer CloseSecureChannel-Anfrage schließt |
| AuditCreateSessionEventType | wenn ein Client eine Sitzung erstellt |
| AuditActivateSessionEventType | wenn ein Client eine Sitzung aktiviert, also sich anmeldet |
Die Ereignisse zu sicheren Kanälen meldet der Server nur für Verbindungen über opc.tcp. Wie in OPC UA festgelegt, erhält ein Client die Audit-Ereignisse nur, wenn er über einen Endpunkt mit dem Modus SignAndEncrypt oder über HTTPS verbunden ist und sein Benutzer der Rolle SecurityAdmin angehört (siehe Rollen).
Weitere Audit-Ereignisse, etwa für Schreibzugriffe, Methodenaufrufe oder Zertifikatsänderungen, meldet der Server nicht. Eigene Audit-Ereignisse kann eine Anwendung nicht melden. Wie ein Client die Ereignisse empfängt, beschreibt der Abschnitt Audit-Ereignisse empfangen.
Wenn Ihre Anwendung ein Protokoll sicherheitsrelevanter Aktionen benötigt, führen Sie es selbst über die Ereignisse des Servers, zum Beispiel SessionActivated für Anmeldungen, SessionActivationFailed für abgelehnte Anmeldungen und RequestValidating für Schreibzugriffe und Methodenaufrufe (siehe Anfragen überwachen):
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 tritt bei jeder abgelehnten Anmeldung ein. Die Eigenschaft Status nennt den Grund, die Eigenschaft Identity die Identität, mit der sich der Client anmelden wollte, ohne Passwort oder Token-Daten. Bei einem Benutzernamen ist sie eine OpcUserNameIdentityTokenInfo mit dem Namen des Benutzers (UserName), bei einem Benutzerzertifikat eine OpcCertificateIdentityTokenInfo mit dem Zertifikat (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 SubHärtung für den Produktivbetrieb
Prüfen Sie vor dem Produktivbetrieb die folgenden Punkte:
- Nur verschlüsselte Endpunkte anbieten: ausschließlich Policies mit
SignAndEncryptund einem aktuellen Algorithmus eintragen (siehe Endpunkte und Netzwerk absichern). - Netzwerkschnittstellen beschränken: die Eigenschaft
Bindingauf die benötigten Adressen begrenzen. - Unbekannte Clientzertifikate ablehnen:
AutoAcceptUntrustedCertificatesdeaktivieren und Clientzertifikate in denTrustedPeerStoreaufnehmen (siehe Clientzertifikate prüfen und vertrauen). - Zertifikate streng prüfen:
CertificateValidationRulesaufRejectSHA1SignedundDisableBackwardsCompatibleValidationstellen. - Eigenes Zertifikat verwalten: ein Zertifikat mit angemessener Schlüssellänge und Laufzeit verwenden,
AutoCreateCertificatedeaktivieren und die Zertifikatspeicher in einem geschützten Verzeichnis ablegen (siehe Eigenes Zertifikat schützen). - Anonymen Zugriff abschalten und Benutzer verwalten: nur bekannte Benutzer zulassen, Passwörter als Hashwerte ablegen und Benutzerzertifikate prüfen (siehe Benutzer anmelden).
- Rechte minimal halten: jedem Benutzer nur die Operationen erteilen, die er braucht, und Rollen gezielt zuordnen (siehe Rollen und Zugriffskontrollmodi).
- Anmeldeversuche begrenzen: die
AuthenticationLimitsan die Zahl der Benutzer anpassen (siehe Anmeldeversuche begrenzen). - Überlastung verhindern:
OperationLimits.AutoDetermineLimitsaktivieren, die Grenzen unterTransportnicht unnötig erhöhen und unerwünschte Anfragen ablehnen (siehe Schutz vor Überlastung). - GDS Push gezielt freischalten: die Rolle
SecurityAdminnur einer Identität mit Zertifikat zuordnen (siehe Zertifikate per GDS verwalten). - Sicherheitsrelevante Aktionen protokollieren: bei Bedarf
IsAuditingEnabledeinschalten, Anmeldungen, abgelehnte Anmeldungen und kritische Anfragen selbst protokollieren (siehe Audit-Ereignisse) und die Kennzahlen des Servers überwachen (siehe Diagnose). - Lizenzschlüssel schützen: den Schlüssel nicht im Klartext in öffentlich zugänglichen Dateien oder Repositories ablegen (siehe Fahrlässige Veröffentlichung).
Zusammengefasst sieht ein so konfigurierter Server wie folgt aus:
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()
