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

HPCC-30037 Check legacy DZ physical permission in ESP services #17658

Merged
merged 1 commit into from
Oct 4, 2023

Conversation

wangkx
Copy link
Member

@wangkx wangkx commented Aug 8, 2023

Type of change:

  • This change is a bug fix (non-breaking change which fixes an issue).
  • This change is a new feature (non-breaking change which adds functionality).
  • This change improves the code (refactor or other change that does not change the functionality)
  • This change fixes warnings (the fix does not alter the functionality or the generated code)
  • This change is a breaking change (fix or feature that will cause existing behavior to change).
  • This change alters the query API (existing queries will have to be recompiled)

Checklist:

  • My code follows the code style of this project.
    • My code does not create any new warnings from compiler, build system, or lint.
  • The commit message is properly formatted and free of typos.
    • The commit message title makes sense in a changelog, by itself.
    • The commit is signed.
  • My change requires a change to the documentation.
    • I have updated the documentation accordingly, or...
    • I have created a JIRA ticket to update the documentation.
    • Any new interfaces or exported functions are appropriately commented.
  • I have read the CONTRIBUTORS document.
  • The change has been fully tested:
    • I have added tests to cover my changes.
    • All new and existing tests passed.
    • I have checked that this change does not introduce memory leaks.
    • I have used Valgrind or similar tools to check for potential issues.
  • I have given due consideration to all of the following potential concerns:
    • Scalability
    • Performance
    • Security
    • Thread-safety
    • Cloud-compatibility
    • Premature optimization
    • Existing deployed queries will not be broken
    • This change fixes the problem, not just the symptom
    • The target branch of this pull request is appropriate for such a change.
  • There are no similar instances of the same problem that should be addressed
    • I have addressed them here
    • I have raised JIRA issues to address them separately
  • This is a user interface / front-end modification
    • I have tested my changes in multiple modern browsers
    • The component(s) render as expected

Smoketest:

  • Send notifications about my Pull Request position in Smoketest queue.
  • Test my draft Pull Request.

Testing:

@github-actions
Copy link

github-actions bot commented Aug 8, 2023

@wangkx wangkx requested a review from jakesmith August 8, 2023 14:21
@wangkx
Copy link
Member Author

wangkx commented Aug 8, 2023

@jakesmith please review the second commit which is based on the commit in #17528.

@jakesmith
Copy link
Member

@wangkx - before I review, now that https://track.hpccsystems.com/browse/HPCC-29246 is merged, can you rebase and change from draft status?

Copy link
Member

@jakesmith jakesmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wangkx - please rebase

@wangkx
Copy link
Member Author

wangkx commented Aug 15, 2023

Rebased

@wangkx wangkx marked this pull request as ready for review August 15, 2023 18:12
@wangkx wangkx requested a review from jakesmith August 15, 2023 18:12
Copy link
Member

@jakesmith jakesmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wangkx - see comment.

}
//Based on the tests, the dfuserver only supports the wildcard inside the file name, like '/path/f*'.
//The dfuserver throws an error if the wildcard is inside the path, like /p*ath/file.
SecAccessFlags permission = getDZFileScopePermissions(context, sourcePlaneReq, path, sourceIPReq, SecAccess_Read);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the check on if (dropZone) line 2016 (and elsewhere throughout these changes), doesn't look right.

It is conflating 2 different issues:

  1. The existence of a valid dropzone definition in the environment (or no match if restrictions are off)
  2. LDAP checking of that dropzone based on PLANE::::... or failing over to legacy in bare-metal if failover is configured.

If the dropzone in 1) doesn't exist (and was allowed to continue because restrictions off), then it should not continue to scope check.
If the dropzone does exist, it should scope check and that scope checking should failover to legacy physical form if failOverToLegacyPhysicalPerms is on.

NB: it is also wrong in dfurun.cpp, it too should not go on to check if an environment dropzone couldn't be found (and was allowed to proceed because restrictions were off).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the dropzone in 1) doesn't exist (and was allowed to continue because restrictions off), then it should not continue to scope check.

@jakesmith We still need to check legacy physical form if the failOverToLegacyPhysicalPerms is on, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only if dropzone exists. And as noted above, dfurun.cpp should do same.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about the Foreign File in dfurun.cpp? Should I check legacy physical form there if the failOverToLegacyPhysicalPerms is on?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that is not dependent on whether there's a dropzone or not.
The comments above are only related to what to do when there's no dropzone found (which implies restrictions off too).

@wangkx
Copy link
Member Author

wangkx commented Sep 5, 2023

@jakesmith please review (I do not keep the previous commit since it is fully changed).

Copy link
Member

@jakesmith jakesmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wangkx - please see comments

if (permission < SecAccess_Read)
throw makeStringExceptionV(ECLWATCH_INVALID_INPUT, "Access DropZone Scope %s %s %s not allowed for user %s (permission:%s). Read Access Required.",
dropZoneName.str(), netAddressStr.str(), pathStr.str(), context.queryUserId(), getSecAccessFlagName(permission));
validateDropZoneReq(context, dropZoneName, netAddressStr, pathStr, SecAccess_Read);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if isDropZoneRestrictionEnabled is disabled?
Can validateDropZoneReq called in this context still throw an error?

Need to make sure all routes are consistent.

It may not be true universally now (?) , but if a dropzone doesn't exist and drop zone restrictions are off, it makes little sense to go on to check any LDAP permissions.

Copy link
Member Author

@wangkx wangkx Sep 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validateDropZoneReq() calls the getDZPathScopePermissions() which handles the isDropZoneRestrictionEnabled if a dropzone cannot be found based on host and path (a bug to be fixed inside the error message). All other DZ permission checks in ESP should go through the getDZPathScopePermissions().
Inside the getDZPathScopePermissions(), if a dropzone name is given, but, a dropzone cannot be found based on the name, an exception is thrown. Should the isDropZoneRestrictionEnabled be checked before throwing the exception (see the dfurun for the similar situation)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment in validateDropZoneReq(), this method is only called for upload/download and maybe 'special' if there is zero expectation for it to be used without a dropzone (since it is in response to the UI presenting a list of files in dropzones).

If there are other situations this could be called - and legacy systems have used upload/download without a corresponding dropzone and with useDropZoneRestriction=false , then you will need to careful you don't break backward compatibility by enforcing it now even with useDropZoneRestriction=false

I think it's probably okay, because used in conjunction with UI only - but the semantics and reasoning needs to be clear (with comments)

throw makeStringExceptionV(ECLWATCH_INVALID_INPUT, "Access DropZone Scope %s %s %s not allowed for user %s (permission:%s). Full Access Required.",
dropZoneName.str(), netAddress.str(), path.str(), ctx.queryUserId(), getSecAccessFlagName(permission));

validateDropZoneReq(ctx, dropZoneName, netAddress, path, SecAccess_Full);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same q. as above

@@ -1920,6 +1920,16 @@ static bool parseUNCPath(const char* sprayPath, StringBuffer& localPath, StringB
return true;
}

static void validateDZFileScopePermission(IEspContext& context, const char* dropZoneName, const char* path, const char* host, SecAccessFlags permissionReq)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this new function is very similar and/or overlapping with the new function you've introduced in ws_fsBinding.cpp.
Which makes it hard to see at a glance why there are multiple functions performing very similar functions and easy to introduce differences and inconsistencies.

At a glance, it looks like the one in ws_fsBinding.cpp, could be calling/reusing this one,
They could do with renaming to clarify their difference too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function calls getDZFileScopePermissions() and the new function in ws_fsBinding.cpp calls getDZPathScopePermissions(). I am going to move (possible rename and merge) them (and the getDZPathScopePermissionsEx()) into TpCommon.cpp.

if (permission < SecAccess_Read)
throw makeStringExceptionV(ECLWATCH_INVALID_INPUT, "Access DropZone Scope %s %s not allowed for user %s (permission:%s). Read Access Required.",
sourcePlaneReq.str(), path, context.queryUserId(), getSecAccessFlagName(permission));
validateDZFileScopePermission(context, sourcePlaneReq, path, sourceIPReq, SecAccess_Read);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think validateDZFileScopePermission will allow access if dropzone restrictions are disabled, whether or not the dropzone exists .... it shouldn't be checking permissions.

But it's not clear that is the case looking at validateDZFileScopePermission.
In these common helper functions, it would be good if it clearly commented the semantics, including what happens when dropzone restrictions are disabled.
Another reason to ensure as much is common as possible (see other comments)

@@ -2975,6 +2979,15 @@ bool CFileSprayEx::onDFUWUFile(IEspContext &context, IEspDFUWUFileRequest &req,
return true;
}

static SecAccessFlags getDZPathScopePermissionsEx(IEspContext &context, const char *dropZoneName, const char *path,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this also looks like a very similar function to other new helper functions introduced (a subset).
All these similar functions should be in one place and utilize each other and be clearer how they behave and how they differ.


//The setExternal() requests the path with a file name. The file name is not used by the getDLFNPermissions().
//If the dropZoneFile is a path only, the dropZoneScope + a faked file name is passed to the setExternal().
StringBuffer pathWithFileName(dropZoneFile);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would help if there were 2 functions one for Path and one for File (getLegacyDZPhysicalFilePerms, getLegacyDZPhysicalPathPerms)
And, rather than this workaround for the lack of support from IDistributedFileDirectory for validating a path that isn't a file, it would be better to add support.
What's missing I think is a simple:

IDistributedFileDirectory::getScopePermissions(const char *scopes, ....) 

method.

If that is added, then the common code for that getLegacyDZPhysicalFilePerms and getLegacyDZPhysicalPathPerms calls , can use a CDfsLogicalFileName and conditionally do:

if (isPath)
    scopesToCheck = dlfn.get();
else
{
    dlfn.getScopes(scopes);
    scopesToCheck = scopes;
}
return queryDistributedFileDirectory().getScopePermissions(scopesToCheck, userDesc);

Copy link
Member

@jakesmith jakesmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wangkx - looks better, a couple of questions surrounding useDropZoneRestriction in the case of upload/download.
Could still do with more clarification (comments) on the different but very similar functions in TpCommon.cpp.

And, I think there's more scope to rationalize them further, but that can wait for another day.

@@ -412,12 +412,7 @@ int CFileSpraySoapBindingEx::downloadFile(IEspContext &context, CHttpRequest* re
pathStr.replace(pathSep=='\\'?'/':'\\', pathSep);
addPathSepChar(pathStr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be slightly clearer to add this after the call to validateDropZoneReq
being here, makes it look like it is required for the call.

return permission;
}

//Validate dropzone host and file path. Also validate dropzone file permission when the file path does not contain a file name.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is used in 2 contexts, download and upload.

Is it correct that it does not check isDropZoneRestrictionEnabled() ?

Maybe it is ok, because it is expected to be serviced by UI interactions that have listed dropzones - so it is wholly unexpected for their to be a request here without a valid dropzone.

If it is okay, it needs a clear comment as to what this is for and why it does not check isDropZoneRestrictionEnabled() in this case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when the file path does not contain a file name.

When rewriting the comment I would avoid using 'when' it makes it sound like it could be either a path to a file or a path without a file.
Consistently calling naming the parameter 'path' or 'filePath' would be clearer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is inside the validateDropZoneHostAndPath() which is called by validateDropZoneReq() and not check isDropZoneRestrictionEnabled(). It should be fixed because the validateDropZoneHostAndPath() is also called by the FileSpray.FileList. I am working on it now.

if (permission < SecAccess_Read)
throw makeStringExceptionV(ECLWATCH_INVALID_INPUT, "Access DropZone Scope %s %s %s not allowed for user %s (permission:%s). Read Access Required.",
dropZoneName.str(), netAddressStr.str(), pathStr.str(), context.queryUserId(), getSecAccessFlagName(permission));
validateDropZoneReq(context, dropZoneName, netAddressStr, pathStr, SecAccess_Read);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment in validateDropZoneReq(), this method is only called for upload/download and maybe 'special' if there is zero expectation for it to be used without a dropzone (since it is in response to the UI presenting a list of files in dropzones).

If there are other situations this could be called - and legacy systems have used upload/download without a corresponding dropzone and with useDropZoneRestriction=false , then you will need to careful you don't break backward compatibility by enforcing it now even with useDropZoneRestriction=false

I think it's probably okay, because used in conjunction with UI only - but the semantics and reasoning needs to be clear (with comments)

if (!isEmptyString(dzName) && getDZPathScopePermissions(context, dzName, directoryStr, nullptr) < SecAccess_Read)
return false;
if (!isEmptyString(dzName) && (getDZPathScopePermsAndLegacyPhysicalPerms(context, dzName, directoryStr, netAddress, SecAccess_Read) < SecAccess_Read))
return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

odd indentation

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fit it

@wangkx
Copy link
Member Author

wangkx commented Sep 19, 2023

@jakesmith please review my changes.

Copy link
Member

@jakesmith jakesmith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wangkx - looks clearer, and ready. 2 trivial spacing issues.
Please address those and squash

throw makeStringExceptionV(ECLWATCH_INVALID_INPUT, "Access DropZone %s %s not allowed for user %s (permission:%s). Read Access Required.",
dropZoneName, sPath.str(), context.queryUserId(), getSecAccessFlagName(permission));
if (isEmptyString(dropZoneName))
dropZoneName= dropZone->queryProp("@name");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trivial/formattnig: spacing around '='

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


Owned<IPropertyTree> dropZone = getDropZoneAndValidateHostAndPath(dropZoneName, host, filePath);
if (dropZone)
dropZoneName= dropZone->queryProp("@name");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trivial/formatting: spacing around '='

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

1. In checkPlaneFilePermissions() (dfurun.cpp), check legacy
physical permission only if the plane name is set.
2. Add getLegacyDZPhysicalPerms() in ESP SMCLib.
3. Call getLegacyDZPhysicalPerms() in validateDropZoneAccess()
which is used by ESP WsFileIO service.
4. In ESP FileSpray service, check legacy physical permission
if plane exists.

Revise based on review
1. Add queryDistributedFileDirectory().getFScopePermissions() and
call it in the getLegacyDZPhysicalPerms().
2. Move validateDropZoneReq(), validateDZFileScopePermission()
and getDZPathScopePermissionsEx()) into TpCommon.cpp.
Rename the getDZPathScopePermissionsEx() to
getDZPathScopePermsAndLegacyPhysicalPerms().
Rename the validateDZFileScopePermission() to
validateDZFileScopePermissions(). Also revise
them.
3. The following 3 files are not called from outside. Change them
from extern to static: getDZPathScopePermissions(),
getDZFileScopePermissions() and getLegacyDZPhysicalPerms().
4. Add comments.
5. Fix a bug in the message for the throwOrLogDropZoneLookUpError().
6. Rename the validateDropZoneHostAndPath() to getDropZoneAndValidateHostAndPath()
and revise it. It is called when uploading/downloading dropzone file and in
WsFileSprayEx::onFileList(). There are 2 issues in the existing code: a. some
code are duplicated with the code in FileSprayEx::onFileList(). b. the
isDropZoneRestrictionEnabled is not used.
7. Rename the validateDZFileScopePermissions() to
   validateDZFileScopePermsAndLegacyPhysicalPerms()
8. Consoliate the code in FileSprayEx::onFileList() by using the
getDropZoneAndValidateHostAndPath().
9. Add validateDZPathScopePermsAndLegacyPhysicalPerms() which contains the common
code for the validateDropZoneReq() and WsFileSprayEx.
10. Revise the code in validateDropZoneReq().
11. Move the addPathSepChar() line, fix an intent bug, and fix a message bug.
12. Add more comments.

Signed-off-by: wangkx <[email protected]>
@wangkx
Copy link
Member Author

wangkx commented Sep 22, 2023

@ghalliday please merge this PR.

@ghalliday ghalliday merged commit bf1ce49 into hpcc-systems:candidate-9.2.x Oct 4, 2023
49 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants