diff --git a/gr_security.md b/gr_security.md new file mode 100644 index 0000000..5595394 --- /dev/null +++ b/gr_security.md @@ -0,0 +1,190 @@ +# Galaxy Repository — Security, Users, Roles & Permissions + +## Overview + +The Galaxy Repository uses a role-based access control (RBAC) system with three layers: +1. **Users** — individual accounts in `user_profile` +2. **Roles** — assigned to users, control what operations they can perform +3. **Security Groups** — assigned to objects/packages, control which users can see/modify them +4. **Security Classification** — per-attribute write protection levels (0-6) + +## 1. Users (`user_profile` table) + +| Column | Type | Description | +|---|---|---| +| `user_profile_id` | int | Primary key | +| `user_profile_name` | nvarchar | Username (e.g., SystemEngineer, Administrator) | +| `user_guid` | uniqueidentifier | Unique identifier for check-out tracking | +| `password_hash` | int | Legacy password hash | +| `crypto_secure_hashed_pwd` | ntext | Encrypted password | +| `default_security_group` | nvarchar | Default security group for new objects | +| `roles` | ntext | Serialized role assignments (binary) | +| `intouch_access_level` | int | InTouch HMI access level | +| `user_full_name` | nvarchar | Display name | +| `default_platform_tag_name` | nvarchar | Default platform for deployments | +| `default_app_engine_tag_name` | nvarchar | Default engine for new objects | +| `default_history_engine_tag_name` | nvarchar | Default historian engine | +| `default_area_tag_name` | nvarchar | Default area for new objects | + +### Current Users + +| ID | Name | Security Group | Roles | Notes | +|---|---|---|---|---| +| 1 | SystemEngineer | Default | (none) | Built-in engineering account | +| 2 | Administrator | Default | Administrator | Has password, admin role | +| 3 | DefaultUser | Default | (assigned) | Default runtime user | + +### User Preferences (`user_preferences` table) + +Stores per-user UI preferences as binary blobs, keyed by `user_profile_id` and `preference_type` (GUID). + +## 2. Roles & Permissions + +Roles are managed through Galaxy runtime attributes, not SQL tables. The Galaxy platform object exposes: + +### Role Management Attributes + +| Attribute | Description | +|---|---| +| `RoleList` | Array of defined role names | +| `AddRole` | Command: create a new role | +| `DeleteRole` | Command: remove a role | +| `CurrentRole` | The active role for the current session | +| `RolePermission` | Array of permission flags per role | +| `TogglePermission` | Command: toggle a permission flag | + +### User-Role Assignment Attributes + +| Attribute | Description | +|---|---| +| `SetUserRole` | Command: assign a role to a user | +| `DelUserRole` | Command: remove a role from a user | +| `ListUserRole` | Command: list roles for a user | + +### Operation Permissions + +| Attribute | Description | +|---|---| +| `OpPermission` | Operation permission list | +| `OpPermissionSGList` | Operation permissions per security group | +| `ToggleOpPermission` | Command: toggle operation permission | + +## 3. Security Groups + +Security groups control which users can access which objects. Each `package` (object version) belongs to a security group. + +### `package.security_group` column + +- Type: nvarchar +- Default: `"Default"` +- All packages in the current Galaxy use the `"Default"` security group + +### Key Stored Procedures + +| Procedure | Description | +|---|---| +| `list_object_by_security_group` | Lists all objects in a given security group | +| `internal_change_objects_security_group` | Bulk-changes security groups for objects | +| `is_package_visible_to_user` | Checks if a user can see a specific package version | + +### Visibility Rules + +- Objects checked out by a user are only visible to that user (exclusive edit) +- `gobject.checked_out_by_user_guid` tracks who has the object checked out +- `is_package_visible_to_user` determines which package version (checked-in vs checked-out) a user sees + +## 4. Security Classification (Attribute-Level) + +Each attribute has a `security_classification` value controlling write access: + +| Value | Level | OPC UA Access | Description | +|---|---|---|---| +| 0 | FreeAccess | ReadWrite | No restrictions | +| 1 | Operate | ReadWrite | Normal operating level (default) | +| 2 | SecuredWrite | ReadOnly | Elevated write access required | +| 3 | VerifiedWrite | ReadOnly | Verified/confirmed write required | +| 4 | Tune | ReadWrite | Tuning-level access | +| 5 | Configure | ReadWrite | Configuration-level access | +| 6 | ViewOnly | ReadOnly | Read-only, no writes | + +### Distribution in Database + +| Classification | Count | Example Attributes | +|---|---|---| +| -1 | 15,905 | Most system attributes (no restriction) | +| 0 | 2,194 | Basic attributes | +| 1 | 1,420 | Description fields, standard attributes | +| 4 | 1,392 | Tune-level (e.g., `_ChangePassword`) | +| 5 | 969 | Configure-level attributes | +| 6 | 2,250 | ViewOnly (e.g., `_ExternalName`) | + +### Lock Types (`dynamic_attribute.lock_type`) + +| Value | Description | +|---|---| +| 0 | Standard (unlocked) | +| Other | Locked by template or system | + +## 5. Authentication + +The Galaxy supports multiple authentication modes, configured via the `AuthenticationMode` attribute on the platform: + +| Mode | Description | +|---|---| +| Galaxy Only | Users authenticate against Galaxy's internal user_profile table | +| OS Group Only | Users authenticate via Windows domain groups | +| Windows Integrated Security | Users authenticate via Windows credentials (SSO) | + +### Related Attributes + +| Attribute | Description | +|---|---| +| `AuthenticationMode` | Active authentication method | +| `SecurityEnabled` | Boolean: whether security enforcement is active | +| `_SecuritySchema` | XML security schema definition (binary) | +| `_MasterSecuritySchema` | Master security schema (binary) | + +## 6. Check-Out/Check-In Security + +Galaxy objects use an exclusive check-out model for editing: + +| `gobject` Column | Description | +|---|---| +| `checked_out_by_user_guid` | GUID of user who has the object checked out | +| `checked_out_package_id` | Package version created for the check-out | +| `checked_in_package_id` | Last checked-in package version | + +Only the user who checked out an object can modify it. Other users see the last checked-in version. + +## 7. Protected Objects + +The `gobject_protected` table lists objects that cannot be modified: +- Contains only `gobject_id` column +- Protects system-critical objects from accidental changes + +## 8. Operation/Workflow Groups + +| Table | Description | +|---|---| +| `ow_group_def` | Operation/workflow group definitions | +| `ow_group_id` | Group ID tracking | +| `ow_group_override` | Group overrides with `property_bitmask` | + +## Summary + +The Galaxy security model operates at four levels: + +``` +Authentication (Galaxy/OS/Windows) + └─ Users (user_profile) + └─ Roles (RoleList, RolePermission) + └─ Security Groups (package.security_group) + └─ Attribute Security Classification (0-6) +``` + +- **Authentication** determines who can connect +- **Roles** determine what operations a user can perform +- **Security Groups** determine which objects a user can access +- **Security Classification** determines per-attribute write permissions + +For the OPC UA server, the most relevant layer is **Security Classification** (already implemented — maps to OPC UA AccessLevel). The server currently uses anonymous access and does not enforce user/role/security-group checks. diff --git a/src/ZB.MOM.WW.LmxOpcUa.Host/Configuration/HistorianConfiguration.cs b/src/ZB.MOM.WW.LmxOpcUa.Host/Configuration/HistorianConfiguration.cs index 7a698b4..2121cf0 100644 --- a/src/ZB.MOM.WW.LmxOpcUa.Host/Configuration/HistorianConfiguration.cs +++ b/src/ZB.MOM.WW.LmxOpcUa.Host/Configuration/HistorianConfiguration.cs @@ -5,6 +5,11 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Configuration /// public class HistorianConfiguration { + /// + /// Gets or sets a value indicating whether OPC UA historical data access is enabled. + /// + public bool Enabled { get; set; } = false; + /// /// Gets or sets the connection string for the Wonderware Historian Runtime database. /// diff --git a/src/ZB.MOM.WW.LmxOpcUa.Host/Configuration/OpcUaConfiguration.cs b/src/ZB.MOM.WW.LmxOpcUa.Host/Configuration/OpcUaConfiguration.cs index 40fe1da..64f7363 100644 --- a/src/ZB.MOM.WW.LmxOpcUa.Host/Configuration/OpcUaConfiguration.cs +++ b/src/ZB.MOM.WW.LmxOpcUa.Host/Configuration/OpcUaConfiguration.cs @@ -34,5 +34,11 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Configuration /// Gets or sets the session timeout, in minutes, before idle client sessions are closed. /// public int SessionTimeoutMinutes { get; set; } = 30; + + /// + /// Gets or sets a value indicating whether alarm tracking is enabled. + /// When enabled, AlarmConditionState nodes are created for alarm attributes and InAlarm transitions are monitored. + /// + public bool AlarmTrackingEnabled { get; set; } = false; } } diff --git a/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/LmxNodeManager.cs b/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/LmxNodeManager.cs index 1d66feb..7b4a76c 100644 --- a/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/LmxNodeManager.cs +++ b/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/LmxNodeManager.cs @@ -23,6 +23,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa private readonly IMxAccessClient _mxAccessClient; private readonly PerformanceMetrics _metrics; private readonly HistorianDataSource? _historianDataSource; + private readonly bool _alarmTrackingEnabled; private readonly string _namespaceUri; // NodeId → full_tag_reference for read/write resolution @@ -123,13 +124,15 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa string namespaceUri, IMxAccessClient mxAccessClient, PerformanceMetrics metrics, - HistorianDataSource? historianDataSource = null) + HistorianDataSource? historianDataSource = null, + bool alarmTrackingEnabled = false) : base(server, configuration, namespaceUri) { _namespaceUri = namespaceUri; _mxAccessClient = mxAccessClient; _metrics = metrics; _historianDataSource = historianDataSource; + _alarmTrackingEnabled = alarmTrackingEnabled; // Wire up data change delivery _mxAccessClient.OnTagValueChanged += OnMxAccessDataChange; @@ -280,7 +283,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa } // Build alarm tracking: create AlarmConditionState for each alarm attribute - foreach (var obj in sorted) + if (_alarmTrackingEnabled) foreach (var obj in sorted) { if (obj.IsArea) continue; if (!attrsByObject.TryGetValue(obj.GobjectId, out var objAttrs)) continue; @@ -354,7 +357,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa } // Auto-subscribe to InAlarm tags so we detect alarm transitions - SubscribeAlarmTags(); + if (_alarmTrackingEnabled) + SubscribeAlarmTags(); Log.Information("Address space built: {Objects} objects, {Variables} variables, {Mappings} tag references, {Alarms} alarm tags", ObjectNodeCount, VariableNodeCount, _nodeIdToTagReference.Count, _alarmInAlarmTags.Count); diff --git a/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/LmxOpcUaServer.cs b/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/LmxOpcUaServer.cs index 3092333..e763836 100644 --- a/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/LmxOpcUaServer.cs +++ b/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/LmxOpcUaServer.cs @@ -16,6 +16,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa private readonly IMxAccessClient _mxAccessClient; private readonly PerformanceMetrics _metrics; private readonly HistorianDataSource? _historianDataSource; + private readonly bool _alarmTrackingEnabled; private LmxNodeManager? _nodeManager; /// @@ -42,19 +43,20 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa /// The runtime client used by the node manager for live data access. /// The metrics collector shared with the node manager. public LmxOpcUaServer(string galaxyName, IMxAccessClient mxAccessClient, PerformanceMetrics metrics, - HistorianDataSource? historianDataSource = null) + HistorianDataSource? historianDataSource = null, bool alarmTrackingEnabled = false) { _galaxyName = galaxyName; _mxAccessClient = mxAccessClient; _metrics = metrics; _historianDataSource = historianDataSource; + _alarmTrackingEnabled = alarmTrackingEnabled; } /// protected override MasterNodeManager CreateMasterNodeManager(IServerInternal server, ApplicationConfiguration configuration) { var namespaceUri = $"urn:{_galaxyName}:LmxOpcUa"; - _nodeManager = new LmxNodeManager(server, configuration, namespaceUri, _mxAccessClient, _metrics, _historianDataSource); + _nodeManager = new LmxNodeManager(server, configuration, namespaceUri, _mxAccessClient, _metrics, _historianDataSource, _alarmTrackingEnabled); var nodeManagers = new List { _nodeManager }; return new MasterNodeManager(server, configuration, null, nodeManagers.ToArray()); diff --git a/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/OpcUaServerHost.cs b/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/OpcUaServerHost.cs index d46ecb8..3b1400b 100644 --- a/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/OpcUaServerHost.cs +++ b/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUa/OpcUaServerHost.cs @@ -159,7 +159,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa certOk = await _application.CheckApplicationInstanceCertificate(false, 2048); } - _server = new LmxOpcUaServer(_config.GalaxyName, _mxAccessClient, _metrics, _historianDataSource); + _server = new LmxOpcUaServer(_config.GalaxyName, _mxAccessClient, _metrics, _historianDataSource, _config.AlarmTrackingEnabled); await _application.Start(_server); Log.Information("OPC UA server started on opc.tcp://localhost:{Port}{EndpointPath} (namespace={Namespace})", diff --git a/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUaService.cs b/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUaService.cs index 0260133..83cfb53 100644 --- a/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUaService.cs +++ b/src/ZB.MOM.WW.LmxOpcUa.Host/OpcUaService.cs @@ -153,7 +153,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Host // Step 8: Create OPC UA server host + node manager var effectiveMxClient = (IMxAccessClient?)_mxAccessClient ?? _mxAccessClientForWiring ?? new NullMxAccessClient(); - var historianDataSource = new Historian.HistorianDataSource(_config.Historian); + var historianDataSource = _config.Historian.Enabled + ? new Historian.HistorianDataSource(_config.Historian) + : null; _serverHost = new OpcUaServerHost(_config.OpcUa, effectiveMxClient, _metrics, historianDataSource); // Step 9-10: Query hierarchy, start server, build address space diff --git a/src/ZB.MOM.WW.LmxOpcUa.Host/appsettings.json b/src/ZB.MOM.WW.LmxOpcUa.Host/appsettings.json index b10bb8d..d259b69 100644 --- a/src/ZB.MOM.WW.LmxOpcUa.Host/appsettings.json +++ b/src/ZB.MOM.WW.LmxOpcUa.Host/appsettings.json @@ -5,7 +5,8 @@ "ServerName": "LmxOpcUa", "GalaxyName": "ZB", "MaxSessions": 100, - "SessionTimeoutMinutes": 30 + "SessionTimeoutMinutes": 30, + "AlarmTrackingEnabled": false }, "MxAccess": { "ClientName": "LmxOpcUa", @@ -31,6 +32,7 @@ "RefreshIntervalSeconds": 10 }, "Historian": { + "Enabled": false, "ConnectionString": "Server=localhost;Database=Runtime;Integrated Security=true;", "CommandTimeoutSeconds": 30, "MaxValuesPerRead": 10000