Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDS: implement ApplicationSelfAdmin privilege in GlobalDiscoverySampleServer #2338

Merged
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
214ede9
implement ApplicationSelfAdmin privilege in GlobalDiscoverySampleServer
romanett Oct 11, 2023
7e6bcde
improve documentation
romanett Oct 11, 2023
8d50547
fix: enforce ApplicationSelfAdminPrivilege
romanett Oct 17, 2023
8d4671d
add Test for ApplicationSelf Admin with disabled flag
romanett Nov 17, 2023
75dcf94
Merge pull request #5 from OPCFoundation/master
romanett Nov 17, 2023
78d3a42
Merge remote-tracking branch 'origin/master' into ImplementApplicatio…
mregen Dec 7, 2023
5dcf8ec
update tests with template for additional required tests
mregen Dec 7, 2023
516c83b
Merge branch 'OPCFoundation:master' into ImplementApplicationSelfAdmi…
romanett Dec 7, 2023
dd4bb7f
prepate Testing of application self admin privilege
romanett Jan 14, 2024
47e3e7a
Set new ApplicationCertificate
romanett Jan 15, 2024
2ca22ee
prepare merge
romanett Jan 18, 2024
a328c6d
merge master
romanett Jan 18, 2024
f82296a
fix issues from merge
romanett Jan 18, 2024
d8abd53
fix regression from merging master (unintentionally inverted if in Ap…
romanett Jan 20, 2024
dd2f7f0
use correct DC for Subject of Certificate
romanett Jan 21, 2024
cf3d367
fix regression from merge, to many roles were allowed
romanett Jan 21, 2024
1a51b43
add Test Cases for KeyPair & Sign with SelfAdminPrivilege
romanett Jan 23, 2024
252ad31
fix access rights of OnGetCertificateGroups method
romanett Jan 24, 2024
7e74111
use hostname for certificate, enable cert validation
romanett Jan 27, 2024
9115f57
refactor to 3 separate tests
romanett Jan 27, 2024
deba6a9
add Test to verify unregistered application is denied
romanett Jan 28, 2024
a3a4cac
validate client cert URI does match applicationUri
romanett Jan 28, 2024
0bcf343
add TrustList Access, still need CertificateStores or change of Appli…
romanett Jan 29, 2024
e550bad
change LinqApplicationsDatabase
romanett Jan 29, 2024
0787a35
cleanup
romanett Jan 29, 2024
a920cd7
Merge remote-tracking branch 'origin/master' into ImplementApplicatio…
romanett Feb 10, 2024
4d9d1fb
fix failing tests
romanett Feb 12, 2024
d8e522f
Merge remote-tracking branch 'origin/master' into ImplementApplicatio…
mregen Feb 14, 2024
ff098a3
add well known roles
romanett Feb 14, 2024
d955bf0
add missing roles to standard gds users to fix failing tests
romanett Feb 14, 2024
c9eb214
Merge pull request #6 from romanett/wellKnownRoles
romanett Feb 14, 2024
57e561a
add new Standard Users
romanett Feb 16, 2024
ecb565e
Merge branch 'wellKnownRoles' into ImplementApplicationSelfAdminPrivi…
romanett Feb 16, 2024
2e68d73
minor optimizations & cleanup & documentation improvements
romanett Feb 18, 2024
6e43018
Merge branch 'master' into ImplementApplicationSelfAdminPrivilege
romanett Feb 18, 2024
283a9c2
Merge remote-tracking branch 'origin/master' into ImplementApplicatio…
mregen Feb 23, 2024
6ef6414
push minor code cleanup
mregen Feb 26, 2024
5449305
check on some warnings
mregen Feb 26, 2024
80cc82b
use discard for unused parameter
romanett Feb 26, 2024
c213b0d
small cleanup
mregen Feb 26, 2024
c9047e7
Merge branch 'ImplementApplicationSelfAdminPrivilege' of https://gith…
mregen Feb 26, 2024
5b00002
fix GDSTestClient
romanett Feb 26, 2024
065b392
improve calls to authorizationHelper to be cleaner
romanett Feb 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* ========================================================================
/* ========================================================================
* Copyright (c) 2005-2020 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class Application
public Application()
{
Certificate = new Dictionary<string, byte[]>();
TrustListId = new Dictionary<string, Guid>();
TrustListId = new Dictionary<string, string>();
}
public uint ID { get; set; }
public Guid ApplicationId { get; set; }
Expand All @@ -59,7 +59,7 @@ public Application()
public string ProductUri { get; set; }
public string ServerCapabilities { get; set; }
public Dictionary<string, byte[]> Certificate { get; }
public Dictionary<string, Guid> TrustListId { get; }
public Dictionary<string, string> TrustListId { get; }
}

[Serializable]
Expand Down Expand Up @@ -711,11 +711,7 @@ string trustListId

if (trustListId != null)
{
var result2 = (from x in CertificateStores where x.Path == trustListId select x).SingleOrDefault();
if (result2 != null)
{
result.TrustListId[certificateType] = result2.TrustListId;
}
result.TrustListId[certificateType] = trustListId;
}
SaveChanges();
}
Expand All @@ -740,20 +736,8 @@ out string trustListId
{
return false;
}

Guid trustListGuid;
if (result.TrustListId.TryGetValue(certificateType, out trustListGuid))
{
var result2 = (from x in CertificateStores where x.TrustListId == trustListGuid select x).SingleOrDefault();
if (result2 != null)
{
trustListId = result2.Path;
return true;
}
}
return result.TrustListId.TryGetValue(certificateType, out trustListId);
}

return false;
}
#endregion

Expand Down
64 changes: 24 additions & 40 deletions Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,33 +162,7 @@ public override NodeId New(ISystemContext context, NodeState node)
}
#endregion

#region Private Methods
private void HasApplicationAdminAccess(ISystemContext context)
{
if (context != null)
{
RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity;

if ((identity == null) || (!identity.Roles.Contains(GdsRole.ApplicationAdmin)))
{
throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Administrator access required.");
}
}
}

private void HasApplicationUserAccess(ISystemContext context)
{
if (context != null)
{
RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity;

if (identity == null)
{
throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application User access required.");
}
}
}

#region Private methods
private NodeId GetTrustListId(NodeId certificateGroupId)
{

Expand Down Expand Up @@ -516,7 +490,7 @@ private ServiceResult OnRegisterApplication(
ApplicationRecordDataType application,
ref NodeId applicationId)
{
HasApplicationAdminAccess(context);
AuthorizationHelper.HasAuthorization(context, new List<Role> { GdsRole.DiscoveryAdmin });

romanett marked this conversation as resolved.
Show resolved Hide resolved
Utils.LogInfo("OnRegisterApplication: {0}", application.ApplicationUri);

Expand All @@ -531,7 +505,7 @@ private ServiceResult OnUpdateApplication(
NodeId objectId,
ApplicationRecordDataType application)
{
HasApplicationAdminAccess(context);
AuthorizationHelper.HasAuthorization(context, new List<Role> { GdsRole.DiscoveryAdmin });

Utils.LogInfo("OnUpdateApplication: {0}", application.ApplicationUri);

Expand All @@ -553,7 +527,7 @@ private ServiceResult OnUnregisterApplication(
NodeId objectId,
NodeId applicationId)
{
HasApplicationAdminAccess(context);
AuthorizationHelper.HasAuthorization(context, new List<Role> { GdsRole.DiscoveryAdmin });

Utils.LogInfo("OnUnregisterApplication: {0}", applicationId.ToString());

Expand Down Expand Up @@ -588,7 +562,7 @@ private ServiceResult OnFindApplications(
string applicationUri,
ref ApplicationRecordDataType[] applications)
{
HasApplicationUserAccess(context);
AuthorizationHelper.HasAuthorization(context, new List<Role> { Role.AuthenticatedUser });
Utils.LogInfo("OnFindApplications: {0}", applicationUri);
applications = m_database.FindApplications(applicationUri);
return ServiceResult.Good;
Expand All @@ -601,7 +575,7 @@ private ServiceResult OnGetApplication(
NodeId applicationId,
ref ApplicationRecordDataType application)
{
HasApplicationUserAccess(context);
AuthorizationHelper.HasAuthorization(context, new List<Role> { Role.AuthenticatedUser, GdsRole.ApplicationSelfAdmin }, applicationId); ;
Utils.LogInfo("OnGetApplication: {0}", applicationId);
application = m_database.GetApplication(applicationId);
return ServiceResult.Good;
Expand Down Expand Up @@ -761,7 +735,7 @@ private ServiceResult OnStartNewKeyPairRequest(
string privateKeyPassword,
ref NodeId requestId)
{
HasApplicationAdminAccess(context);
AuthorizationHelper.HasAuthorization(context, new List<Role> { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }, applicationId); ;

var application = m_database.GetApplication(applicationId);

Expand Down Expand Up @@ -880,7 +854,7 @@ private ServiceResult OnStartSigningRequest(
byte[] certificateRequest,
ref NodeId requestId)
{
HasApplicationAdminAccess(context);
AuthorizationHelper.HasAuthorization(context, new List<Role> { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }, applicationId); ;

var application = m_database.GetApplication(applicationId);

Expand Down Expand Up @@ -961,7 +935,7 @@ private ServiceResult OnFinishRequest(
signedCertificate = null;
issuerCertificates = null;
privateKey = null;
HasApplicationAdminAccess(context);
AuthorizationHelper.HasAuthorization(context, new List<Role> { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }, applicationId); ;

var application = m_database.GetApplication(applicationId);
if (application == null)
Expand Down Expand Up @@ -1115,6 +1089,8 @@ out privateKeyPassword

m_database.SetApplicationCertificate(applicationId, m_certTypeMap[certificateGroup.CertificateType], signedCertificate);

m_database.SetApplicationTrustLists(applicationId, m_certTypeMap[certificateGroup.CertificateType], certificateGroup.Configuration.TrustedListPath);

m_request.AcceptRequest(requestId, signedCertificate);

return ServiceResult.Good;
Expand All @@ -1127,7 +1103,7 @@ public ServiceResult OnGetCertificateGroups(
NodeId applicationId,
ref NodeId[] certificateGroupIds)
{
HasApplicationUserAccess(context);
AuthorizationHelper.HasAuthorization(context, new List<Role> { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }, applicationId);

var application = m_database.GetApplication(applicationId);

Expand Down Expand Up @@ -1155,7 +1131,7 @@ public ServiceResult OnGetTrustList(
NodeId certificateGroupId,
ref NodeId trustListId)
{
HasApplicationUserAccess(context);
AuthorizationHelper.HasAuthorization(context, new List<Role> { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }, applicationId);

var application = m_database.GetApplication(applicationId);

Expand Down Expand Up @@ -1188,7 +1164,7 @@ public ServiceResult OnGetCertificateStatus(
NodeId certificateTypeId,
ref Boolean updateRequired)
{
HasApplicationUserAccess(context);
AuthorizationHelper.HasAuthorization(context, new List<Role> { Role.AuthenticatedUser, GdsRole.ApplicationSelfAdmin }, applicationId);

var application = m_database.GetApplication(applicationId);

Expand Down Expand Up @@ -1354,11 +1330,19 @@ protected void SetCertificateGroupNodes(ICertificateGroup certificateGroup)
certificateGroup.DefaultTrustList,
certificateGroup.Configuration.TrustedListPath,
certificateGroup.Configuration.IssuerListPath,
new TrustList.SecureAccess(HasApplicationUserAccess),
new TrustList.SecureAccess(HasApplicationAdminAccess));
new TrustList.SecureAccess(HasTrustListAccess),
new TrustList.SecureAccess(HasTrustListAccess));
}
}

#region AuthorizationHelpers
private void HasTrustListAccess(ISystemContext context, string trustedStorePath)
{
AuthorizationHelper.HasTrustListAccess(context, trustedStorePath, m_certTypeMap, m_database);
}
#endregion


private ServiceResult VerifyApprovedState(CertificateRequestState state)
{
switch (state)
Expand Down
115 changes: 86 additions & 29 deletions Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Opc.Ua.Server;
using Opc.Ua.Gds.Server.Database;
using Opc.Ua.Server.UserDatabase;
using System.Linq;
using Opc.Ua.Security.Certificates;

namespace Opc.Ua.Gds.Server
{
Expand Down Expand Up @@ -117,8 +117,7 @@ protected override MasterNodeManager CreateMasterNodeManager(IServerInternal ser
/// </remarks>
protected override ServerProperties LoadServerProperties()
{
ServerProperties properties = new ServerProperties
{
ServerProperties properties = new ServerProperties {
ManufacturerName = "Some Company Inc",
ProductName = "Global Discovery Server",
ProductUri = "http://somecompany.com/GlobalDiscoveryServer",
Expand Down Expand Up @@ -201,28 +200,10 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg
{
if (VerifyPassword(userNameToken))
{
if (userNameToken.UserName == "sysadmin")
{
// Server configuration administrator, manages the GDS server security
args.Identity = new SystemConfigurationIdentity(new UserIdentity(userNameToken));
Utils.LogInfo("SystemConfigurationAdmin Token Accepted: {0}", args.Identity.DisplayName);
return;
}
IEnumerable<Role> roles = m_userDatabase.GetUserRoles(userNameToken.UserName);
//GdsAdmin
if (roles.Contains(GdsRole.ApplicationAdmin))
{
args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), new List<Role> { GdsRole.ApplicationAdmin });
Utils.LogInfo("ApplicationAdmin Token Accepted: {0}", args.Identity.DisplayName);
return;
}
//GdsUser
if (roles.Contains(GdsRole.ApplicationUser))
{
args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), new List<Role> { GdsRole.ApplicationUser });
Utils.LogInfo("ApplicationUser Token Accepted: {0}", args.Identity.DisplayName);
return;
}

args.Identity = new GdsRoleBasedIdentity(new UserIdentity(userNameToken), roles);
return;
}
}

Expand All @@ -235,10 +216,61 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg
// todo: is cert listed in admin list? then
// role = GdsRole.ApplicationAdmin;

Utils.LogInfo("X509 Token Accepted: {0} as {1}", args.Identity.DisplayName, GdsRole.ApplicationUser);
args.Identity = new RoleBasedIdentity(new UserIdentity(x509Token), new List<Role> { GdsRole.ApplicationUser });
Utils.LogInfo("X509 Token Accepted: {0} as {1}", args.Identity.DisplayName, Role.AuthenticatedUser);
args.Identity = new GdsRoleBasedIdentity(new UserIdentity(x509Token), new List<Role> { Role.AuthenticatedUser });
return;
}

//check if applicable for application self admin privilege
if (session.ClientCertificate != null)
{
if (VerifiyApplicationRegistered(session))
{
ImpersonateAsApplicationSelfAdmin(session, args);
}
}
}

/// <summary>
/// Verifies if an Application is registered with the provided certificate at at the GDS
/// </summary>
/// <param name="session">the session</param>
/// <returns></returns>
private bool VerifiyApplicationRegistered(Session session)
{
X509Certificate2 applicationInstanceCertificate = session.ClientCertificate;
bool applicationRegistered = false;

Uri applicationUri = Utils.ParseUri(session.SessionDiagnostics.ClientDescription.ApplicationUri);
X509Utils.DoesUrlMatchCertificate(applicationInstanceCertificate, applicationUri);

//get access to GDS configuration section to find out ApplicationCertificatesStorePath
GlobalDiscoveryServerConfiguration configuration = Configuration.ParseExtension<GlobalDiscoveryServerConfiguration>();
if (configuration == null)
{
configuration = new GlobalDiscoveryServerConfiguration();
}
//check if application certificate is in the Store of the GDS
using (ICertificateStore ApplicationsStore = CertificateStoreIdentifier.OpenStore(configuration.ApplicationCertificatesStorePath))
{
var matchingCerts = ApplicationsStore.FindByThumbprint(applicationInstanceCertificate.Thumbprint).Result;

if (matchingCerts.Contains(applicationInstanceCertificate))
applicationRegistered = true;
}
//check if application certificate is revoked
using (ICertificateStore AuthoritiesStore = CertificateStoreIdentifier.OpenStore(configuration.AuthoritiesStorePath))
{
var crls = AuthoritiesStore.EnumerateCRLs().Result;
foreach (X509CRL crl in crls)
{
if (crl.IsRevoked(applicationInstanceCertificate))
{
applicationRegistered = false;
}
}
}
return applicationRegistered;
}

/// <summary>
Expand Down Expand Up @@ -295,10 +327,35 @@ private bool VerifyPassword(UserNameIdentityToken userNameToken)
/// </summary>
private void RegisterDefaultUsers()
{
m_userDatabase.CreateUser("sysadmin", "demo", new List<Role> { GdsRole.ApplicationAdmin, Role.SecurityAdmin, Role.ConfigureAdmin });
m_userDatabase.CreateUser("appadmin", "demo", new List<Role> { GdsRole.ApplicationAdmin });
m_userDatabase.CreateUser("appuser", "demo", new List<Role> { GdsRole.ApplicationUser });
m_userDatabase.CreateUser("sysadmin", "demo", new List<Role> { GdsRole.CertificateAuthorityAdmin, GdsRole.DiscoveryAdmin, Role.SecurityAdmin, Role.ConfigureAdmin });
m_userDatabase.CreateUser("appadmin", "demo", new List<Role> { Role.AuthenticatedUser, GdsRole.CertificateAuthorityAdmin, GdsRole.DiscoveryAdmin });
m_userDatabase.CreateUser("appuser", "demo", new List<Role> { Role.AuthenticatedUser });

m_userDatabase.CreateUser("DiscoveryAdmin", "demo", new List<Role> { Role.AuthenticatedUser, GdsRole.DiscoveryAdmin });
m_userDatabase.CreateUser("CertificateAuthorityAdmin", "demo", new List<Role> { Role.AuthenticatedUser, GdsRole.CertificateAuthorityAdmin });
}

/// <summary>
/// Impersonates the current Session as ApplicationSelfAdmin
/// </summary>
/// <param name="session">the current session</param>
/// <param name="args">the impersonateEventArgs</param>
private void ImpersonateAsApplicationSelfAdmin(Session session, ImpersonateEventArgs args)
{
string applicationUri = session.SessionDiagnostics.ClientDescription.ApplicationUri;
ApplicationRecordDataType[] application = m_database.FindApplications(applicationUri);
if (application == null || application.Length != 1)
{
Utils.LogInfo("Cannot login based on ApplicationInstanceCertificate, no uniqure result for Application with URI: {0}", applicationUri);
return;
}
NodeId applicationId = application.FirstOrDefault().ApplicationId;
Utils.LogInfo("Application {0} accepted based on ApplicationInstanceCertificate as ApplicationSelfAdmin",
applicationUri);
args.Identity = new GdsRoleBasedIdentity(new UserIdentity(), new List<Role> { GdsRole.ApplicationSelfAdmin }, applicationId);
return;
}

#endregion

#region Private Fields
Expand Down
Loading
Loading