-
Notifications
You must be signed in to change notification settings - Fork 304
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-28950 JFrog for Manifest/Resource File Management #17674
Conversation
https://track.hpccsystems.com/browse/HPCC-28950 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jackdelv Don't be put off by the number of comments! Feel free to ask me about any of them.
ecl/hql/hqlmanifest.cpp
Outdated
@@ -198,6 +198,31 @@ void ResourceManifest::loadResource(const char *filepath, MemoryBuffer &content) | |||
read(fio, 0, (size32_t) f->size(), content); | |||
} | |||
|
|||
void queryJfrog(const char *localpath, IPropertyTree &item) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: We use query as a prefix for functions that return a value, e.g. queryName().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to callJfrog.
ecl/hql/hqlmanifest.cpp
Outdated
getFileNameOnly(filename, false); | ||
StringBuffer filepath; | ||
StringBuffer machine; | ||
if (repo[0] == '/' && repo[1] == '/') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are the security implications of this change? As far as I can see it would allow me to overwrite any local file with something stored in a jfrog repository.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, jfrog would overwrite the file with a resource from artifactory. I have changed it to write to a temporary directory.
ecl/hql/hqlmanifest.cpp
Outdated
if (user && getSecretValue(secretValue.clear(), "jfrog", user, "key", true)) | ||
jfrog_cmd.appendf("--user=%s --password=%s ", user, secretValue.trimRight().str()); | ||
jfrog_cmd.appendf("\"%s%s\" %s >/dev/null 2>&1", filepath.str(), filename.str(), localpath); | ||
system(jfrog_cmd.str()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use invoke_program() or runExternalCommand() rather than system.
What happens if the jfrog command fails?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am now using runExternalCommand to run the jfrog cli and checking for any errors.
ecl/hql/hqlmanifest.cpp
Outdated
splitFilename(repo, nullptr, &filepath, nullptr, nullptr); | ||
StringBuffer jfrog_cmd("export CI=true; jf rt dl --flat --quiet "); | ||
if (!machine.isEmpty()) | ||
jfrog_cmd.appendf("--url=http:%s/artifactory ", machine.str()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
http is not secure. Ideally we should be verifying that the server is valid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree and JFrog has steps for setting up an artifactory repository to work with https, but it is not available on the open source version that I am running of artifactory.
ecl/hql/hqlmanifest.cpp
Outdated
const char *repo = item.queryProp("@repository"); | ||
if(repo) | ||
{ | ||
StringBuffer filename(localpath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Should localpath be passed in, or should this function be responsible for creating a temporary local path in a valid location? (I don't know what typical requirements would be.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, I have created a temporary directory under /tmp/HPCCSystems/jfrog_downloads/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be using
getTempFilePath(tmpDirName, "jfrog", nullptr);
or similar to create the temporary directory.
ecl/hql/hqlmanifest.cpp
Outdated
StringBuffer secretValue; | ||
const char *user = item.queryProp("@user"); | ||
if (user && getSecretValue(secretValue.clear(), "jfrog", user, "key", true)) | ||
jfrog_cmd.appendf("--user=%s --password=%s ", user, secretValue.trimRight().str()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Passing secrets on the command line isn't very secure. Is there a better alternative way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only other way is to have the user configure jfrog at the command line with jf c add.
ecl/hqlcpp/hqlres.cpp
Outdated
@@ -99,6 +99,31 @@ static void loadResource(const char *filepath, MemoryBuffer &content) | |||
read(fio, 0, (size32_t) f->size(), content); | |||
} | |||
|
|||
static void queryJfrog(const char *localpath, IPropertyTree &item) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A duplicate of the code above? It should only be in one place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed duplicate code and included hqlmanifest header file.
ecl/hql/hqlmanifest.cpp
Outdated
@@ -223,6 +248,7 @@ void ResourceManifest::addToArchive(IPropertyTree *archive) | |||
IPropertyTree *resTree = additionalFiles->addPropTree("Resource", createPTree("Resource")); | |||
resTree->setProp("@filename", filename); | |||
resTree->setProp("@md5", md5); | |||
queryJfrog(filename, item); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we want to be downloading from jfrog when we create the archives - otherwise that defeats the object of putting off downloading the resource.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure where else to download from jfrog. Wouldn't any other time be too late?
ecl/hql/hqlmanifest.cpp
Outdated
@@ -240,6 +266,7 @@ void ResourceManifest::addToArchive(IPropertyTree *archive) | |||
const char *filepath = item.queryProp("@originalFilename"); | |||
resTree->setProp("@originalFilename", filepath); | |||
resTree->setProp("@resourcePath", respath); | |||
queryJfrog(filepath, item); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't need to write the resource to a known filename - it could read it into the content. Otherwise there is the issue of cleaning up the downloads.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure what you mean by this. The only way to access the contents in jfrog artifactory is to download the files.
ecl/hql/hqlmanifest.cpp
Outdated
jfrog_cmd.appendf("--user=%s --password=%s ", user, secretValue.trimRight().str()); | ||
jfrog_cmd.appendf("\"%s%s\" %s >/dev/null 2>&1", filepath.str(), filename.str(), localpath); | ||
system(jfrog_cmd.str()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also need an option to verify that the hash for an entry we have downloaded matches a hash specified in the resource.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added option for adding md5 hash of resource in callJfrog.
@jackdelv also note some of the tests are failing. It is worth going back to your PRs after a day to check if they are showing up any problems. |
@ghalliday back to you. I have responded to all of your comments and left a couple of questions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jackdelv sorry for not re-reviewing this earlier. I have added a few comments, but I think it would be worth scheduling some time to discuss the feature a bit more before responding to them.
I will email the potential users to check exactly what would be useful for this functionality.
ecl/hql/hqlmanifest.cpp
Outdated
@@ -221,9 +267,12 @@ void ResourceManifest::addToArchive(IPropertyTree *archive) | |||
if (!additionalFiles->hasProp(xpath.str())) | |||
{ | |||
IPropertyTree *resTree = additionalFiles->addPropTree("Resource", createPTree("Resource")); | |||
StringBuffer filepath(filename); | |||
callJfrog(filepath, absFilename, item); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clearer to have the if (repo) check outside the call - otherwise when reading this code is poses the question "why is it calling jfrog for a local file.)
Alternatively rename the function to make it clearer what it is doing. E.g.
checkForDownloadFromJfrog()
if the test was outside it would still be better renamed getResourceFromJfrog()
or similar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to getResourceFromJfrog() and moved check outside the call.
ecl/hql/hqlmanifest.cpp
Outdated
StringBuffer userValue; | ||
StringBuffer keyValue; | ||
const char *repoName = item.queryProp("@repoName"); | ||
if (repoName && getSecretValue(keyValue, "jfrog", repoName, "key", true) && getSecretValue(userValue, "jfrog", repoName, "user", true)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a more secure way of passing the credentials? Command lines can be read by other processes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The user can do a one time configuration of the artifactory connection to avoid passing credentials each time we download from JFrog. I removed the code for passing credentials via the command line.
ecl/hql/hqlmanifest.cpp
Outdated
jfrog_cmd.appendf("--url=http:%s/artifactory ", machine.str()); | ||
StringBuffer userValue; | ||
StringBuffer keyValue; | ||
const char *repoName = item.queryProp("@repoName"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
repoName isn't very descriptive. How does it relate to repository?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I intended it to allow for multiple artifactory servers to be used. I removed it because it is no longer needed now that the connection is configured once by the user.
ecl/hql/hqlmanifest.cpp
Outdated
jfrog_cmd.appendf("\"%s%s\" %s", filepath.str(), filename.str(), downloadPath.str()); | ||
StringBuffer errOut; | ||
StringBuffer jfrogOut; | ||
if (system("export CI=true") != 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised this works. The correct way is to use the version of runExternalCommand which allows environment variables to be passed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
ecl/hql/hqlmanifest.cpp
Outdated
throw makeStringExceptionV(0, "Error exporting jfrog setting: export CI=true"); | ||
else if(runExternalCommand(errOut, jfrogOut, jfrog_cmd.str(), nullptr) != 0) | ||
throw makeStringExceptionV(0, "Error loading resource from jfrog: \n\"%s\"\n %s", jfrogOut.str(), errOut.str()); | ||
const char *md5 = item.queryProp("@md5"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check would make sense for non-jfrog as well - so I would move out of this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed this check because it already occurs outside of the JFrog call.
ecl/hql/hqlmanifest.cpp
Outdated
if (repoName && getSecretValue(keyValue, "jfrog", repoName, "key", true) && getSecretValue(userValue, "jfrog", repoName, "user", true)) | ||
jfrog_cmd.appendf("--user=%s --password=%s ", userValue.trimRight().str(), keyValue.trimRight().str()); | ||
jfrog_cmd.appendf("\"%s%s\" %s", filepath.str(), filename.str(), downloadPath.str()); | ||
StringBuffer errOut; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
personally I would add a few newlines to break up the function and make it easier to read. For instance before this line, which is the beginning of the code to execute the command, and line 220 which is the start of the code to build up the command string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
ecl/hql/hqlmanifest.cpp
Outdated
const char *repo = item.queryProp("@repository"); | ||
if(repo) | ||
{ | ||
StringBuffer filename(localpath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be using
getTempFilePath(tmpDirName, "jfrog", nullptr);
or similar to create the temporary directory.
@ghalliday I addressed all your comments and added some changes. Back to you. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jackdelv a few more comments.
@@ -1,3 +1,3 @@ | |||
<Manifest> | |||
<Resource type='jar' filename='javaembed_ex7.jar'/> | |||
<Resource type='jar' filename='javaembed_ex7.jar' repository='/generic-local/javaFiles/' md5='d933045e7fed10ff9ccddfc9743b0538'/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this is for testing and it should be reverted before merging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that will be reverted.
ecl/hql/hqlmanifest.cpp
Outdated
getTempFilePath(downloadPath, "jfrog", nullptr); | ||
downloadPath.append(filename); | ||
|
||
StringBuffer jfrogCmd("jf rt dl --flat --quiet "); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is authentication handled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The server address and credentials are configured with the JFrog cli before making any calls to Artifactory. The connections from getResourceFromJfrog use the new configuration instead of having to pass the credentials at the command line.
ecl/hql/hqlmanifest.cpp
Outdated
@@ -221,9 +242,13 @@ void ResourceManifest::addToArchive(IPropertyTree *archive) | |||
if (!additionalFiles->hasProp(xpath.str())) | |||
{ | |||
IPropertyTree *resTree = additionalFiles->addPropTree("Resource", createPTree("Resource")); | |||
StringBuffer filepath(filename); | |||
if (item.queryProp("@repository")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a case where you do not want to fetch the artefact at this point. This is before the query is sent to the system - so it would be downloaded by the user, added to the archive and then uploaded. The aim of this change is to avoid that step.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I think I understand. I am just not sure where the resource is used after being added to the archive.
6b30f8a
to
e0673a8
Compare
@ghalliday I moved the call to getResourceFromJfrog to when the resource is fetch from the archive rather than storing it in the archive. |
Let's discuss this verbally. We missed out clearly talking through the requirements first - we need to do that and then see where we stand. |
@ghalliday Authentication for Jfrog is now set up using the secrets vault. A Jfrog config file is created rather than passing the credentials through the command line. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jackdelv This looks close. A few final comments.
Also a general comment - it is better not to rebase your branch unless you need to because it makes it harder to track which changes have taken place since the last review.
Also please can you update the jira to contain a summary of what has been implemented - e.g. the format and options in the resource definition to use the feature.
It will also need some changes to ensure the secrets are accessible from containerized builds, but I will add a separate comment for that,
common/dllserver/CMakeLists.txt
Outdated
@@ -38,7 +38,8 @@ include_directories ( | |||
./../../rtl/eclrtl | |||
./../../system/include | |||
./../../dali/base | |||
./../../system/jlib | |||
./../../system/jlib | |||
./../../ecl/hql |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This dll shouldn't really have a dependency on hql.
I think the cleanest solution is to move getResourceFromJfrog into jlib. Either into a new file, or added to something like jutil.hpp/cpp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved getResourceFromJfrog to jutil.
ecl/hql/hqlmanifest.cpp
Outdated
|
||
void getResourceFromJfrog(StringBuffer &localPath, IPropertyTree &item) | ||
{ | ||
addSecretToJfrog(item); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
security: Ensure this file is deleted when the command has executed (for success or failure)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
ecl/hql/hqlmanifest.cpp
Outdated
],\n\ | ||
\"version\": \"6\"\n}\n", server, server, server, server, server, server, user, password.str()); | ||
|
||
StringBuffer configPath(getenv("HOME")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use getHomeDir() in jmisc.hpp - which will also work on windows.
I assume this is a location defined by jfrog.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is the location where the JFrog CLI expects the config file to be if there are no credentials passed through the command line.
ecl/hql/hqlmanifest.cpp
Outdated
{ | ||
StringBuffer password; | ||
const char *user = item.queryProp("@jfrogUser"); | ||
getSecretValue(password, "jfrog", user, "password", true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@afishbeck Do you think this is reasonable to add a new category to access jfrog secrets? Are we going to continue to have new secret categories?
Would it be better to have an "external" category with a key of "jfrog:"? What other alternatives are there. The only other similar example at the moment is "git", but maybe we will end up with docker, or other examples.
Thoughts?
@jackdelv great, it looks like it is ready to merge. Please can you
Actually... we still need to think about making the secrets available in container builds from the helm files. I will ask Tony exactly what he thinks, but I think worth squashing all changes so far. |
@ghalliday I added a description to this JIRA detailing the options and adding the secrets. If I left anything out please let me know. Here is the documentation JIRA: https://track.hpccsystems.com/browse/HPCC-31310. Back to you. |
I think the final change is to update the helm files to ensure the secrets are available in the cloud version. Two places need to change (the same place that "git" secrets are currently supported): |
@ghalliday Squashed. |
Type of change:
Checklist:
Smoketest:
Testing: