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 9 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
55 changes: 44 additions & 11 deletions Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,14 @@
if (context != null)
{
RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity;

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

Expand All @@ -181,10 +184,40 @@
{
RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity;

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

Check warning on line 194 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L194

Added line #L194 was not covered by tests
}
}

Check warning on line 196 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L196

Added line #L196 was not covered by tests

/// <summary>
/// checks if the given Application can be modified with the current context
/// </summary>
/// <param name="context">the current context</param>
/// <param name="applicationId">the application to modify</param>
private void HasApplicationSelfAdminPrivilege(ISystemContext context, NodeId applicationId)
{
if (context != null)
{
RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity;
if (identity != null)
{
throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application User access required.");
//administrator/user has full access
if (identity.Role == GdsRole.ApplicationAdmin || identity.Role == GdsRole.ApplicationUser)
return;
//not administrator/user only has access to own application
if (identity.ApplicationId == applicationId)
{
return;

Check warning on line 216 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L216

Added line #L216 was not covered by tests
}
}
throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privielge or " +
"Application Administrator/User access required.");
}
}

Expand Down Expand Up @@ -760,7 +793,7 @@
string privateKeyPassword,
ref NodeId requestId)
{
HasApplicationAdminAccess(context);
HasApplicationSelfAdminPrivilege(context, applicationId);

var application = m_database.GetApplication(applicationId);

Expand Down Expand Up @@ -879,7 +912,7 @@
byte[] certificateRequest,
ref NodeId requestId)
{
HasApplicationAdminAccess(context);
HasApplicationSelfAdminPrivilege(context, applicationId);

var application = m_database.GetApplication(applicationId);

Expand Down Expand Up @@ -960,7 +993,7 @@
signedCertificate = null;
issuerCertificates = null;
privateKey = null;
HasApplicationAdminAccess(context);
HasApplicationSelfAdminPrivilege(context, applicationId);

var application = m_database.GetApplication(applicationId);
if (application == null)
Expand Down Expand Up @@ -1126,7 +1159,7 @@
NodeId applicationId,
ref NodeId[] certificateGroupIds)
{
HasApplicationUserAccess(context);
HasApplicationSelfAdminPrivilege(context, applicationId);

var application = m_database.GetApplication(applicationId);

Expand Down Expand Up @@ -1154,7 +1187,7 @@
NodeId certificateGroupId,
ref NodeId trustListId)
{
HasApplicationUserAccess(context);
HasApplicationSelfAdminPrivilege(context, applicationId);

var application = m_database.GetApplication(applicationId);

Expand Down Expand Up @@ -1187,7 +1220,7 @@
NodeId certificateTypeId,
ref Boolean updateRequired)
{
HasApplicationUserAccess(context);
HasApplicationSelfAdminPrivilege(context, applicationId);

var application = m_database.GetApplication(applicationId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
using System.Threading;
using Opc.Ua.Server;
using Opc.Ua.Gds.Server.Database;
using Opc.Ua.Security.Certificates;
using Org.BouncyCastle.Asn1.Cmp;
using System.Linq;
using System.Data;

namespace Opc.Ua.Gds.Server
{
Expand Down Expand Up @@ -238,9 +242,61 @@
// role = GdsRole.ApplicationAdmin;

Utils.LogInfo("X509 Token Accepted: {0} as {1}", args.Identity.DisplayName, role.ToString());
args.Identity = new RoleBasedIdentity(new UserIdentity(x509Token), role);

string applicationUri = session.SessionDiagnostics.ClientDescription.ApplicationUri;
args.Identity = new RoleBasedIdentity(new UserIdentity(x509Token), role, applicationUri);

Check warning on line 247 in Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs#L246-L247

Added lines #L246 - L247 were not covered by tests
return;
}

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

Check warning on line 256 in Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs#L256

Added line #L256 was not covered by tests
}
}
}

/// <summary>
/// Verifies if an Application is registered with the provided certificate at at the GDS
/// </summary>
/// <param name="applicationInstanceCertificate">the certificate to check for</param>
/// <returns></returns>
private bool VerifiyApplicationRegistered(X509Certificate2 applicationInstanceCertificate)
{
bool applicationRegistered = false;
//get access to GDS configuration section to find out ApplicationCertificatesStorePath
GlobalDiscoveryServerConfiguration configuration = Configuration.ParseExtension<GlobalDiscoveryServerConfiguration>();
if (configuration == null)
{
configuration = new GlobalDiscoveryServerConfiguration();

Check warning on line 273 in Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs#L273

Added line #L273 was not covered by tests
}
//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 warning on line 281 in Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs#L281

Added line #L281 was not covered by tests
}
//check if application certificate is revoked
using (ICertificateStore AuthoritiesStore = CertificateStoreIdentifier.OpenStore(configuration.AuthoritiesStorePath))
{
var crls = AuthoritiesStore.EnumerateCRLs().Result;
foreach (X509CRL crl in crls)
{
var revokedCertificates = crl.RevokedCertificates;
foreach (RevokedCertificate revokedCertificate in revokedCertificates)
{
if(revokedCertificate.SerialNumber == applicationInstanceCertificate.SerialNumber)
{
applicationRegistered = false;

Check warning on line 294 in Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs#L294

Added line #L294 was not covered by tests
}
}
}
}
return applicationRegistered;
}

/// <summary>
Expand Down Expand Up @@ -301,6 +357,28 @@
m_userDatabase.CreateUser("appadmin", "demo", GdsRole.ApplicationAdmin);
m_userDatabase.CreateUser("appuser", "demo", GdsRole.ApplicationUser);
}

/// <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);

Check warning on line 369 in Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs#L368-L369

Added lines #L368 - L369 were not covered by tests
if(application == null || application.Length>1 || application.Length<1)
{
Utils.LogInfo("Cannot login based on ApplicationInstanceCertificate, no uniqure result for Application with URI: {0}", applicationUri);
return;

Check warning on line 373 in Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs#L372-L373

Added lines #L372 - L373 were not covered by tests
}
NodeId applicationId = application.FirstOrDefault().ApplicationId;
Utils.LogInfo("Application {0} accepted based on ApplicationInstanceCertificate as ApplicationSelfAdmin",
applicationUri);
args.Identity = new RoleBasedIdentity(new UserIdentity(), GdsRole.ApplicationSelfAdmin, applicationId);
return;

Check warning on line 379 in Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs#L375-L379

Added lines #L375 - L379 were not covered by tests
}

#endregion

#region Private Fields
Expand Down
23 changes: 22 additions & 1 deletion Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/

using System;
using System.Xml;

namespace Opc.Ua.Gds.Server
Expand All @@ -44,7 +45,12 @@
/// <summary>
/// The GDS application user.
/// </summary>
ApplicationUser
ApplicationUser,

/// <summary>
/// Can manage the own Certificates and pull trust list
/// </summary>
ApplicationSelfAdmin
}

/// <summary>
Expand All @@ -54,6 +60,7 @@
{
private IUserIdentity m_identity;
private GdsRole m_role;
private NodeId m_applicationId;

/// <summary>
/// Initialize the role based identity.
Expand All @@ -64,6 +71,12 @@
m_role = role;
}

public RoleBasedIdentity(IUserIdentity identity, GdsRole role, NodeId applicationId)
:this(identity, role)

Check warning on line 75 in Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs#L75

Added line #L75 was not covered by tests
{
m_applicationId = applicationId;
}

Check warning on line 78 in Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs#L77-L78

Added lines #L77 - L78 were not covered by tests

/// <inheritdoc/>
public NodeIdCollection GrantedRoleIds
{
Expand All @@ -79,6 +92,14 @@
get { return m_role; }
}

/// <summary>
/// The applicationId in case the ApplicationSelfAdminPrivilege is used
/// </summary>
public NodeId ApplicationId
{
get { return m_applicationId; }

Check warning on line 100 in Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs#L100

Added line #L100 was not covered by tests
}

/// <inheritdoc/>
public string DisplayName
{
Expand Down
77 changes: 74 additions & 3 deletions Tests/Opc.Ua.Gds.Tests/ClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,9 @@ out byte[][] issuerCertificates
} while (requestBusy);
}

[Test, Order(511)]


[Test, Order(512)]
public void FinishInvalidNewKeyPairRequests()
{
AssertIgnoreTestWithoutInvalidRegistration();
Expand Down Expand Up @@ -943,6 +945,68 @@ public void GetGoodCertificateGroupsAndTrustLists()
}
}

[Test, Order(620)]
public void FailToGetGoodCertificateGroupsWithoutPriviledges()
{
AssertIgnoreTestWithoutGoodRegistration();
AssertIgnoreTestWithoutGoodNewKeyPairRequest();

//connect to GDS without Admin Privilege
ConnectGDS(false);

foreach (var application in m_goodApplicationTestSet)
{
if (application.Certificate != null)
{
var sre = Assert.Throws<ServiceResultException>(() => m_gdsClient.GDSClient.GetCertificateGroups(application.ApplicationRecord.ApplicationId));
Assert.NotNull(sre);
Assert.AreEqual(StatusCodes.BadUserAccessDenied, sre.StatusCode, sre.Result.ToString());

}
}
}

[Test, Order(630)]
public void GetGoodCertificateGroupsAsSelfAdmin()
{
AssertIgnoreTestWithoutGoodRegistration();
AssertIgnoreTestWithoutGoodNewKeyPairRequest();


// connect with gds issued certificate
m_gdsClient.RegisterTestClientAtGds();
ConnectGDS(false, true);

// ensure access to other applications is denied
foreach (var application in m_goodApplicationTestSet)
{
if (application.Certificate != null)
{
var sre = Assert.Throws<ServiceResultException>(() => m_gdsClient.GDSClient.GetCertificateGroups(application.ApplicationRecord.ApplicationId));
Assert.NotNull(sre);
Assert.AreEqual(StatusCodes.BadUserAccessDenied, sre.StatusCode, sre.Result.ToString());
}
}

Assert.Ignore("TODO: Tests for other logic is not implemented yet!");

// use self registered application and get the group / trust lists
var certificateGroups = m_gdsClient.GDSClient.GetCertificateGroups(m_gdsClient.OwnApplicationTestData.ApplicationRecord.ApplicationId);
foreach (var certificateGroup in certificateGroups)
{
var trustListId = m_gdsClient.GDSClient.GetTrustList(m_gdsClient.OwnApplicationTestData.ApplicationRecord.ApplicationId, certificateGroup);
// Opc.Ua.TrustListDataType
var trustList = m_gdsClient.GDSClient.ReadTrustList(trustListId);
Assert.NotNull(trustList);
}

// self issue a certificate and read it back

// self issue a public/private key pair and read it back


}

[Test, Order(690)]
public void GetGoodCertificateStatus()
{
Expand Down Expand Up @@ -1034,11 +1098,18 @@ public void ServerLogResult()
#endregion

#region Private Methods
private void ConnectGDS(bool admin,
private void ConnectGDS(bool admin, bool anonymous = false,
[System.Runtime.CompilerServices.CallerMemberName] string memberName = ""
)
{
m_gdsClient.GDSClient.AdminCredentials = admin ? m_gdsClient.AdminUser : m_gdsClient.AppUser;
if (anonymous)
{
m_gdsClient.GDSClient.AdminCredentials = m_gdsClient.Anonymous;
}
else
{
m_gdsClient.GDSClient.AdminCredentials = admin ? m_gdsClient.AdminUser : m_gdsClient.AppUser;
}
m_gdsClient.GDSClient.Connect(m_gdsClient.GDSClient.EndpointUrl).Wait();
TestContext.Progress.WriteLine($"GDS Client({admin}) connected -- {memberName}");
}
Expand Down
Loading
Loading