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

fix: Introduce time duration for apkam keys to auto expire #2085

Merged
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4ffe975
fix: Introduce time duration for apkam keys to auto expire
sitaram-kalluri Sep 4, 2024
4ab376c
fix: Use ?? for null check for apkamKeysExpiryDuration
sitaram-kalluri Sep 12, 2024
5d7edca
Merge remote-tracking branch 'origin/trunk' into 2074-introducing-aut…
sitaram-kalluri Sep 12, 2024
4c558ac
feat: Update dependent package versions and CHANGELOG.md
sitaram-kalluri Sep 13, 2024
9726357
Merge remote-tracking branch 'origin/trunk' into 2074-introducing-aut…
sitaram-kalluri Sep 13, 2024
bc14389
feat: Remove unnecessary null-aware operator
sitaram-kalluri Sep 13, 2024
49aef1d
Merge branch 'trunk' into 2074-introducing-auto-expiry-and-time-to-bi…
sitaram-kalluri Sep 16, 2024
90dff59
fix: Terminate the inbound connection when apkam keys are expired
sitaram-kalluri Sep 18, 2024
b5198a7
Merge remote-tracking branch 'origin/trunk' into 2074-introducing-aut…
sitaram-kalluri Sep 18, 2024
d8ca29e
fix: Add functional test to verify connection closes when apkam keys …
sitaram-kalluri Sep 19, 2024
1352ef0
fix: Add future delay to fix the failing unit test
sitaram-kalluri Sep 19, 2024
08ec802
Merge branch 'trunk' into 2074-introducing-auto-expiry-and-time-to-bi…
sitaram-kalluri Sep 19, 2024
8bbc86b
fix: Introduce EnrollmentManager class through which all interaction …
sitaram-kalluri Sep 23, 2024
ffea8d8
Merge remote-tracking branch 'origin/trunk' into 2074-introducing-aut…
sitaram-kalluri Sep 23, 2024
d6a0f4a
fix: Remove un-used variables from info_verb_handler.dart
sitaram-kalluri Sep 23, 2024
2516eff
Merge remote-tracking branch 'origin/trunk' into 2074-introducing-aut…
sitaram-kalluri Sep 24, 2024
7f9ff68
fix: Include EnrollmentManager changes into enrollment delete operati…
sitaram-kalluri Sep 24, 2024
a0fe5a7
fix: Add dart docs for the enrollment_manager.dart and remove comment…
sitaram-kalluri Sep 24, 2024
2c549f2
Merge branch 'trunk' into 2074-introducing-auto-expiry-and-time-to-bi…
sitaram-kalluri Sep 25, 2024
75b016c
Merge branch 'trunk' into 2074-introducing-auto-expiry-and-time-to-bi…
gkc Sep 25, 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
3 changes: 3 additions & 0 deletions packages/at_secondary_server/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## 3.0.50
- fix: Enhance namespace authorisation check to verify when namespace has a period in it
- feat: Enable expiration of APKAM keys based on the specified duration.

## 3.0.49
- feat: Enforce superset access check for approving apps
- fix: respect isEncrypted:false if supplied in the notify: command, and
Expand All @@ -9,6 +11,7 @@
notifications from the server to the client.
- build[deps]: Upgraded the following package:
- at_persistence_secondary_server to v3.0.63

## 3.0.48
- feat Add expiresAt and availableAt params to notify:list response

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class EnrollDataStoreValue {
EnrollRequestType? requestType;
EnrollApproval? approval;
String? encryptedAPKAMSymmetricKey;
Duration apkamKeysExpiryDuration = Duration(milliseconds: 0);

EnrollDataStoreValue(
this.sessionId, this.appName, this.deviceName, this.apkamPublicKey);
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'dart:convert';

import 'package:at_commons/at_commons.dart';
import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart';
import 'package:at_secondary/src/constants/enroll_constants.dart';
import 'package:at_secondary/src/enroll/enroll_datastore_value.dart';
import 'package:at_secondary/src/server/at_secondary_impl.dart';
import 'package:at_secondary/src/utils/secondary_util.dart';
import 'package:at_utils/at_logger.dart';

class EnrollmentManager {
final SecondaryKeyStore _keyStore;

final logger = AtSignLogger('AtSecondaryServer');

EnrollmentManager(this._keyStore);

Future<EnrollDataStoreValue> get(String enrollmentId) async {
String enrollmentKey = buildEnrollmentKey(enrollmentId);
try {
AtData enrollData = await _keyStore.get(enrollmentKey);
EnrollDataStoreValue enrollDataStoreValue =
EnrollDataStoreValue.fromJson(jsonDecode(enrollData.data!));

if (!SecondaryUtil.isActiveKey(enrollData)) {
enrollDataStoreValue.approval?.state = EnrollmentStatus.expired.name;
}

return enrollDataStoreValue;
} on KeyNotFoundException {
logger.severe('$enrollmentKey does not exist in the keystore');
rethrow;
}
}

String buildEnrollmentKey(String enrollmentId) {
return '$enrollmentId.$newEnrollmentKeyPattern.$enrollManageNamespace${AtSecondaryServerImpl.getInstance().currentAtSign}';
}

Future<void> put(String enrollmentId, AtData atData) async {
String enrollmentKey = buildEnrollmentKey(enrollmentId);
await _keyStore.put(enrollmentKey, atData, skipCommit: true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import 'dart:math';
import 'package:at_commons/at_commons.dart';
import 'package:at_lookup/at_lookup.dart';
import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart';
import 'package:at_secondary/src/caching/cache_refresh_job.dart';
import 'package:at_secondary/src/caching/cache_manager.dart';
import 'package:at_secondary/src/caching/cache_refresh_job.dart';
import 'package:at_secondary/src/connection/inbound/inbound_connection_manager.dart';
import 'package:at_secondary/src/connection/outbound/outbound_client_manager.dart';
import 'package:at_secondary/src/connection/stream_manager.dart';
import 'package:at_secondary/src/enroll/enrollment_manager.dart';
import 'package:at_secondary/src/exception/global_exception_handler.dart';
import 'package:at_secondary/src/notification/notification_manager_impl.dart';
import 'package:at_secondary/src/notification/queue_manager.dart';
Expand All @@ -31,8 +32,8 @@ import 'package:at_server_spec/at_server_spec.dart';
import 'package:at_server_spec/at_verb_spec.dart';
import 'package:at_utils/at_utils.dart';
import 'package:crypton/crypton.dart';
import 'package:uuid/uuid.dart';
import 'package:meta/meta.dart';
import 'package:uuid/uuid.dart';

/// [AtSecondaryServerImpl] is a singleton class which implements [AtSecondaryServer]
class AtSecondaryServerImpl implements AtSecondaryServer {
Expand Down Expand Up @@ -106,6 +107,7 @@ class AtSecondaryServerImpl implements AtSecondaryServer {
late var atCommitLogCompactionConfig;
late var atAccessLogCompactionConfig;
late var atNotificationCompactionConfig;
late EnrollmentManager enrollmentManager;

@override
void setExecutor(VerbExecutor executor) {
Expand Down Expand Up @@ -169,6 +171,9 @@ class AtSecondaryServerImpl implements AtSecondaryServer {
secondaryPersistenceStore = SecondaryPersistenceStoreFactory.getInstance()
.getSecondaryPersistenceStore(currentAtSign)!;

// Initialize enrollment manager
enrollmentManager = EnrollmentManager(secondaryKeyStore);

//Commit Log Compaction
commitLogCompactionJobInstance =
AtCompactionJob(_commitLog, secondaryPersistenceStore);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ abstract class AbstractVerbHandler implements VerbHandler {
if (getVerb().requiresAuth() && !atConnectionMetadata.isAuthenticated) {
throw UnAuthenticatedException('Command cannot be executed without auth');
}
// This check verifies whether the enrollment is active on the already APKAM authenticated existing connection
// and terminates if the enrollment is expired.
// At this stage, the enrollmentId is not set to the InboundConnectionMetadata for the new connections.
// This will not terminate an un-authenticated connection when attempting to execute a PKAM verb with an expired enrollmentId.
(bool, Response) isEnrollmentActive =
await _verifyIfEnrollmentIsActive(response, atConnectionMetadata);
if (isEnrollmentActive.$1 == false) {
await atConnection.close();
return isEnrollmentActive.$2;
}
try {
// Parse the command
var verbParams = parse(command);
Expand All @@ -73,6 +83,52 @@ abstract class AbstractVerbHandler implements VerbHandler {
}
}

/// When authenticated with the APKAM keys, checks if the enrollment is active.
/// Returns true if the enrollment is active; otherwise, returns false.
Future<(bool, Response)> _verifyIfEnrollmentIsActive(
Response response, AtConnectionMetaData atConnectionMetadata) async {
// When authenticated with legacy keys, enrollment id is null. APKAM expiry does not
// apply to such connections. Therefore, return true.
if ((atConnectionMetadata as InboundConnectionMetadata).enrollmentId ==
null) {
logger.finest(
"Enrollment id is not found. Returning true from _verifyIfEnrollmentIsActive");
return (true, response);
}
try {
EnrollDataStoreValue enrollDataStoreValue =
await AtSecondaryServerImpl.getInstance()
.enrollmentManager
.get(atConnectionMetadata.enrollmentId!);
// If the enrollment status is expired, then the enrollment is not active. Return false.
if (enrollDataStoreValue.approval?.state ==
EnrollmentStatus.expired.name) {
logger.severe(
'The enrollment id: ${atConnectionMetadata.enrollmentId} is expired. Closing the connection');
response
..isError = true
..errorCode = 'AT0028'
..errorMessage =
'The enrollment id: ${(atConnectionMetadata).enrollmentId} is expired. Closing the connection';
return (false, response);
}
// The expired enrollments are removed from the keystore. In such cases, KeyNotFoundException is
// thrown. Return false.
} on KeyNotFoundException {
logger.severe(
'The enrollment id: ${atConnectionMetadata.enrollmentId} is expired. Closing the connection');
response
..isError = true
..errorCode = 'AT0028'
..errorMessage =
'The enrollment id: ${(atConnectionMetadata).enrollmentId} is expired. Closing the connection';
return (false, response);
}
logger.finest(
"Enrollment id ${atConnectionMetadata.enrollmentId} is active. Returning true from _verifyIfEnrollmentIsActive");
return (true, response);
}

/// Return the instance of the current verb
///@return instance of [Verb]
Verb getVerb();
Expand All @@ -93,9 +149,7 @@ abstract class AbstractVerbHandler implements VerbHandler {
AtData enrollData = await keyStore.get(enrollmentKey);
EnrollDataStoreValue enrollDataStoreValue =
EnrollDataStoreValue.fromJson(jsonDecode(enrollData.data!));
if (!SecondaryUtil.isActiveKey(enrollData) &&
enrollDataStoreValue.approval!.state !=
EnrollmentStatus.approved.name) {
if (!SecondaryUtil.isActiveKey(enrollData)) {
enrollDataStoreValue.approval?.state = EnrollmentStatus.expired.name;
}
return enrollDataStoreValue;
Expand Down Expand Up @@ -144,21 +198,20 @@ abstract class AbstractVerbHandler implements VerbHandler {
return true;
}

// Step 1: From the enrollmentId fetch the enrollment details
final enrollmentKey = '${inboundConnectionMetadata.enrollmentId}'
'.$newEnrollmentKeyPattern'
'.$enrollManageNamespace'
'${AtSecondaryServerImpl.getInstance().currentAtSign}';
EnrollDataStoreValue enrollDataStoreValue;

try {
enrollDataStoreValue = await getEnrollDataStoreValue(enrollmentKey);
enrollDataStoreValue = await AtSecondaryServerImpl.getInstance()
.enrollmentManager
.get(inboundConnectionMetadata.enrollmentId!);
} on KeyNotFoundException {
logger.shout('Could not retrieve enrollment data for $enrollmentKey');
logger.shout(
'Could not retrieve enrollment data for ${inboundConnectionMetadata.enrollmentId}');
return false;
}

bool isValid = _applyEnrollmentValidations(
enrollDataStoreValue, enrollmentKey, operation, atKey, namespace);
enrollDataStoreValue, operation, atKey, namespace);
if (isValid == false) {
return isValid;
}
Expand Down Expand Up @@ -254,18 +307,14 @@ abstract class AbstractVerbHandler implements VerbHandler {
return (authorisedNamespace, access);
}

bool _applyEnrollmentValidations(
EnrollDataStoreValue enrollDataStoreValue,
String enrollmentKey,
String operation,
String? atKey,
String? namespace) {
bool _applyEnrollmentValidations(EnrollDataStoreValue enrollDataStoreValue,
String operation, String? atKey, String? namespace) {
// Only approved enrollmentId is authorised to perform operations. Return false for enrollments
// which are not approved.
if (enrollDataStoreValue.approval?.state !=
EnrollmentStatus.approved.name) {
logger.warning('Enrollment state for $enrollmentKey'
' is ${enrollDataStoreValue.approval?.state}');
// logger.warning('Enrollment state for $enrollmentKey'
// ' is ${enrollDataStoreValue.approval?.state}');
return false;
}
// Only the enrollmentId with access to "__manage" namespace can approve, deny, revoke
Expand Down
Loading