Bläddra i källkod

HPCC-20391 Dafilesrv support for secure file access

Signed-off-by: Jake Smith <jake.smith@lexisnexisrisk.com>
Jake Smith 6 år sedan
förälder
incheckning
09d6102d46
4 ändrade filer med 130 tillägg och 21 borttagningar
  1. 1 0
      common/remote/CMakeLists.txt
  2. 117 18
      common/remote/sockfile.cpp
  3. 4 2
      common/remote/sockfile.hpp
  4. 8 1
      dali/dafilesrv/dafilesrv.cpp

+ 1 - 0
common/remote/CMakeLists.txt

@@ -58,6 +58,7 @@ include_directories (
          ./../../system/jhtree
          ./../../rtl/eclrtl
          ./../../system/security/securesocket
+         ./../../system/security/cryptohelper
          ./../../testing/unittests
          ./../../rtl/include
     )

+ 117 - 18
common/remote/sockfile.cpp

@@ -53,6 +53,11 @@
 #include "rtlcommon.hpp"
 #include "rtlformat.hpp"
 
+#include "jflz.hpp"
+#include "digisign.hpp"
+
+using namespace cryptohelper;
+
 
 #define SOCKET_CACHE_MAX 500
 
@@ -181,7 +186,7 @@ struct dummyReadWrite
 // backward compatible modes
 typedef enum { compatIFSHnone, compatIFSHread, compatIFSHwrite, compatIFSHexec, compatIFSHall} compatIFSHmode;
 
-static const char *VERSTRING= "DS V2.2"       // dont forget FILESRV_VERSION in header
+static const char *VERSTRING= "DS V2.3"       // dont forget FILESRV_VERSION in header
 #ifdef _WIN32
 "Windows ";
 #else
@@ -1820,7 +1825,7 @@ public:
         MemoryBuffer actualTypeInfo;
         if (!dumpTypeInfo(actualTypeInfo, actual->querySerializedDiskMeta()->queryTypeInfo()))
             throw MakeStringException(0, "Format not supported by remote read");
-        request.append(",\n \"inputBin\": \"");
+        request.append(",\n \"inputBin\" : \"");
         JBASE64_Encode(actualTypeInfo.toByteArray(), actualTypeInfo.length(), request, false);
         request.append("\"");
         if (actual != projected)
@@ -4421,10 +4426,101 @@ IRemoteActivity *createRemoteIndexCount(IPropertyTree &actNode)
     return new CRemoteIndexCountActivity(actNode);
 }
 
-IRemoteActivity *createRemoteActivity(IPropertyTree &actNode)
+void checkExpiryTime(IPropertyTree &metaInfo)
 {
-    const char *kindStr = actNode.queryProp("kind");
+    const char *expiryTime = metaInfo.queryProp("expiryTime");
+    if (isEmptyString(expiryTime))
+        throwStringExceptionV(0, "createRemoteActivity: invalid expiry specification");
+    CDateTime expiryTimeDt;
+    expiryTimeDt.setString(expiryTime);
+    CDateTime nowDt;
+    nowDt.setNow();
+    if (nowDt >= expiryTimeDt)
+        throwStringExceptionV(0, "createRemoteActivity: authorization expired");
+}
+
+void verifyMetaInfo(IPropertyTree &actNode, bool authorizedOnly, const IPropertyTree *keyPairInfo)
+{
+    if (!authorizedOnly) // if configured false, allows unencrypted meta info
+    {
+        if (actNode.hasProp("fileName"))
+            return;
+    }
+    StringBuffer metaInfoB64;
+    actNode.getProp("metaInfo", metaInfoB64);
+    if (0 == metaInfoB64.length())
+        throwStringExceptionV(0, "createRemoteActivity: missing metaInfo");
+
+    MemoryBuffer compressedMetaInfoMb;
+    JBASE64_Decode(metaInfoB64.str(), compressedMetaInfoMb);
+    MemoryBuffer decompressedMetaInfoMb;
+    fastLZDecompressToBuffer(decompressedMetaInfoMb, compressedMetaInfoMb);
+    Owned<IPropertyTree> metaInfoEnvelope = createPTree(decompressedMetaInfoMb);
+
+    Owned<IPropertyTree> metaInfo;
+#ifdef _USE_OPENSSL
+    MemoryBuffer metaInfoBlob;
+    metaInfoEnvelope->getPropBin("metaInfoBlob", metaInfoBlob);
+
+    bool isSigned = metaInfoBlob.length() != 0;
+    if (authorizedOnly && !isSigned)
+        throwStringExceptionV(0, "createRemoteActivity: unathorized");
+
+    if (isSigned)
+    {
+        metaInfo.setown(createPTree(metaInfoBlob));
+
+        // version for future use
+        unsigned securityVersion = metaInfo->getPropInt("version");
+
+        const char *keyPairName = metaInfo->queryProp("keyPairName");
 
+        StringBuffer metaInfoSignature;
+        if (!metaInfoEnvelope->getProp("signature", metaInfoSignature))
+            throwStringExceptionV(0, "createRemoteActivity: missing signature");
+
+        VStringBuffer keyPairPath("KeyPair[@name=\"%s\"]", keyPairName);
+        IPropertyTree *keyPair = keyPairInfo->queryPropTree(keyPairPath);
+        if (!keyPair)
+            throwStringExceptionV(0, "createRemoteActivity: missing key pair definition");
+        const char *publicKeyFName = keyPair->queryProp("@publicKey");
+        if (isEmptyString(publicKeyFName))
+            throwStringExceptionV(0, "createRemoteActivity: missing public key definition");
+        Owned<CLoadedKey> publicKey = loadPublicKeyFromFile(publicKeyFName, nullptr); // NB: if cared could cache loaded keys
+        if (!digiVerify(metaInfoSignature, decompressedMetaInfoMb.length(), decompressedMetaInfoMb.bytes(), *publicKey))
+            throwStringExceptionV(0, "createRemoteActivity: signature verification failed");
+
+        checkExpiryTime(*metaInfo);
+    }
+    else
+#endif
+        metaInfo.set(metaInfoEnvelope);
+
+    IPropertyTree *fileInfo = metaInfo->queryPropTree("FileInfo");
+    assertex(fileInfo);
+
+    // extra filename based on part/copy.
+    assertex(actNode.hasProp("filePart"));
+    unsigned partNum = actNode.getPropInt("filePart");
+    assertex(partNum);
+    unsigned partCopy = actNode.getPropInt("filePartCopy", 1);
+
+    VStringBuffer xpath("Part[%u]/Copy[%u]/@filePath", partNum, partCopy);
+    StringBuffer partFileName;
+    fileInfo->getProp(xpath, partFileName);
+    if (!partFileName.length())
+        throwStringExceptionV(0, "createRemoteActivity: invalid file info");
+
+    actNode.setProp("fileName", partFileName.str());
+    verifyex(actNode.removeProp("metaInfo")); // no longer needed
+}
+
+IRemoteActivity *createRemoteActivity(IPropertyTree &actNode, bool authorizedOnly, const IPropertyTree *keyPairInfo)
+{
+    verifyMetaInfo(actNode, authorizedOnly, keyPairInfo);
+
+    const char *partFileName = actNode.queryProp("fileName");
+    const char *kindStr = actNode.queryProp("kind");
     ThorActivityKind kind = TAKnone;
     if (kindStr)
     {
@@ -4437,10 +4533,6 @@ IRemoteActivity *createRemoteActivity(IPropertyTree &actNode)
         // else - auto-detect
     }
 
-    const char *fileName = actNode.queryProp("fileName");
-    if (isEmptyString(fileName))
-        throw MakeStringException(0, "createRemoteActivity: fileName missing");
-
     Owned<IRemoteActivity> activity;
     switch (kind)
     {
@@ -4462,14 +4554,14 @@ IRemoteActivity *createRemoteActivity(IPropertyTree &actNode)
         default: // auto-detect file format
         {
             const char *action = actNode.queryProp("action");
-            if (isIndexFile(fileName))
+            if (isIndexFile(partFileName))
             {
                 if (!isEmptyString(action))
                 {
                     if (streq("count", action))
                         activity.setown(createRemoteIndexCount(actNode));
                     else
-                        throwStringExceptionV(0, "Unknown action '%s' on index '%s'", action, fileName);
+                        throwStringExceptionV(0, "Unknown action '%s' on index '%s'", action, partFileName);
                 }
                 else
                     activity.setown(createRemoteIndexRead(actNode));
@@ -4481,7 +4573,7 @@ IRemoteActivity *createRemoteActivity(IPropertyTree &actNode)
                     if (streq("count", action))
                         throwStringExceptionV(0, "Remote Disk Counts currently unsupported");
                     else
-                        throwStringExceptionV(0, "Unknown action '%s' on flat file '%s'", action, fileName);
+                        throwStringExceptionV(0, "Unknown action '%s' on flat file '%s'", action, partFileName);
                 }
                 else
                     activity.setown(createRemoteDiskRead(actNode));
@@ -4493,11 +4585,11 @@ IRemoteActivity *createRemoteActivity(IPropertyTree &actNode)
     return activity.getClear();
 }
 
-IRemoteActivity *createOutputActivity(IPropertyTree &requestTree)
+IRemoteActivity *createOutputActivity(IPropertyTree &requestTree, bool authorizedOnly, const IPropertyTree *keyPairInfo)
 {
     IPropertyTree *actNode = requestTree.queryPropTree("node");
     assertex(actNode);
-    return createRemoteActivity(*actNode);
+    return createRemoteActivity(*actNode, authorizedOnly, keyPairInfo);
 }
 
 #define MAX_KEYDATA_SZ 0x10000
@@ -5139,6 +5231,8 @@ class CRemoteFileServer : implements IRemoteFileServer, public CInterface
     CClientStatsTable clientStatsTable;
     atomic_t globallasttick;
     unsigned targetActiveThreads;
+    bool authorizedOnly;
+    Owned<IPropertyTree> keyPairInfo;
 
     int getNextHandle()
     {
@@ -5290,8 +5384,8 @@ public:
 
     IMPLEMENT_IINTERFACE
 
-    CRemoteFileServer(unsigned maxThreads, unsigned maxThreadsDelayMs, unsigned maxAsyncCopy)
-        : asyncCommandManager(maxAsyncCopy), stdCmdThrottler("stdCmdThrotlter"), slowCmdThrottler("slowCmdThrotlter")
+    CRemoteFileServer(unsigned maxThreads, unsigned maxThreadsDelayMs, unsigned maxAsyncCopy, bool _authorizedOnly, IPropertyTree *_keyPairInfo)
+        : asyncCommandManager(maxAsyncCopy), stdCmdThrottler("stdCmdThrotlter"), slowCmdThrottler("slowCmdThrotlter"), authorizedOnly(_authorizedOnly), keyPairInfo(_keyPairInfo)
     {
         lasthandle = 0;
         selecthandler.setown(createSocketSelectHandler(NULL));
@@ -6263,7 +6357,7 @@ public:
             replyLimit = requestTree->getPropInt64("replyLimit", defaultDaFSReplyLimitKB) * 1024;
 
             // In future this may be passed the request and build a chain of activities and return sink.
-            outputActivity.setown(createOutputActivity(*requestTree));
+            outputActivity.setown(createOutputActivity(*requestTree, authorizedOnly, keyPairInfo));
 
             Owned<CRemoteRequest> remoteRequest = new CRemoteRequest(outputFormat, replyLimit, outputActivity);
 
@@ -7164,12 +7258,17 @@ public:
 };
 
 
-IRemoteFileServer * createRemoteFileServer(unsigned maxThreads, unsigned maxThreadsDelayMs, unsigned maxAsyncCopy)
+IRemoteFileServer * createRemoteFileServer(unsigned maxThreads, unsigned maxThreadsDelayMs, unsigned maxAsyncCopy, bool authorizedOnly, IPropertyTree *keyPairInfo)
 {
 #if SIMULATE_PACKETLOSS
     errorSimulationOn = false;
 #endif
-    return new CRemoteFileServer(maxThreads, maxThreadsDelayMs, maxAsyncCopy);
+
+// NB: if no OOPENSSL, no authorization checks
+#ifndef _USE_OPENSSL
+    authorizedOnly = false;
+#endif
+    return new CRemoteFileServer(maxThreads, maxThreadsDelayMs, maxAsyncCopy, authorizedOnly, keyPairInfo);
 }
 
 

+ 4 - 2
common/remote/sockfile.hpp

@@ -50,6 +50,8 @@ enum ThrottleClass
 #define DEFAULT_SLOWCMD_THROTTLECPULIMIT 75
 #define DEFAULT_SLOWCMD_THROTTLEQUEUELIMIT 1000
 
+#define DEFAULT_AUTHORIZED_ONLY false
+
 interface IRemoteFileServer : extends IInterface
 {
     virtual void run(DAFSConnectCfg connectMethod, SocketEndpoint &listenep, unsigned sslPort=0) = 0;
@@ -59,7 +61,7 @@ interface IRemoteFileServer : extends IInterface
     virtual StringBuffer &getStats(StringBuffer &stats, bool reset) = 0;
 };
 
-#define FILESRV_VERSION 22 // don't forget VERSTRING in sockfile.cpp
+#define FILESRV_VERSION 23 // don't forget VERSTRING in sockfile.cpp
 
 interface IKeyManager;
 interface IDelayedFile;
@@ -68,7 +70,7 @@ extern REMOTE_API IFile * createRemoteFile(SocketEndpoint &ep,const char * _file
 extern REMOTE_API unsigned getRemoteVersion(ISocket * _socket, StringBuffer &ver);
 extern REMOTE_API unsigned stopRemoteServer(ISocket * _socket);
 extern REMOTE_API const char *remoteServerVersionString();
-extern REMOTE_API IRemoteFileServer * createRemoteFileServer(unsigned maxThreads=DEFAULT_THREADLIMIT, unsigned maxThreadsDelayMs=DEFAULT_THREADLIMITDELAYMS, unsigned maxAsyncCopy=DEFAULT_ASYNCCOPYMAX);
+extern REMOTE_API IRemoteFileServer * createRemoteFileServer(unsigned maxThreads=DEFAULT_THREADLIMIT, unsigned maxThreadsDelayMs=DEFAULT_THREADLIMITDELAYMS, unsigned maxAsyncCopy=DEFAULT_ASYNCCOPYMAX, bool authorizedOnly=DEFAULT_AUTHORIZED_ONLY, IPropertyTree *keyPairInfo=nullptr);
 extern REMOTE_API int setDafsTrace(ISocket * socket,byte flags);
 extern REMOTE_API int setDafsThrottleLimit(ISocket * socket, ThrottleClass throttleClass, unsigned throttleLimit, unsigned throttleDelayMs, unsigned throttleCPULimit, unsigned queueLimit, StringBuffer *errMsg=NULL);
 extern REMOTE_API bool enableDafsAuthentication(bool on);

+ 8 - 1
dali/dafilesrv/dafilesrv.cpp

@@ -380,8 +380,10 @@ int main(int argc,char **argv)
     unsigned throttleSlowDelayMs = DEFAULT_SLOWCMD_THROTTLEDELAYMS;
     unsigned throttleSlowCPULimit = DEFAULT_SLOWCMD_THROTTLECPULIMIT;
     unsigned throttleSlowQueueLimit = DEFAULT_SLOWCMD_THROTTLEQUEUELIMIT;
+    bool authorizedOnly = DEFAULT_AUTHORIZED_ONLY;
 
     Owned<IPropertyTree> env = getHPCCEnvironment();
+    IPropertyTree *keyPairInfo = nullptr;
     if (env)
     {
         StringBuffer dafilesrvPath("Software/DafilesrvProcess");
@@ -406,6 +408,8 @@ int main(int argc,char **argv)
             throttleSlowCPULimit = daFileSrv->getPropInt("@throttleSlowCPULimit", DEFAULT_SLOWCMD_THROTTLECPULIMIT);
             throttleSlowQueueLimit = daFileSrv->getPropInt("@throttleSlowQueueLimit", DEFAULT_SLOWCMD_THROTTLEQUEUELIMIT);
 
+            authorizedOnly = daFileSrv->getPropBool("@authorizedOnly", DEFAULT_AUTHORIZED_ONLY);
+
             // any overrides by Instance definitions?
             // NB: This won't work if netAddress is "." or if we start supporting hostnames there
             StringBuffer ipStr;
@@ -427,8 +431,11 @@ int main(int argc,char **argv)
                 throttleSlowDelayMs = dafileSrvInstance->getPropInt("@throttleSlowDelayMs", throttleSlowDelayMs);
                 throttleSlowCPULimit = dafileSrvInstance->getPropInt("@throttleSlowCPULimit", throttleSlowCPULimit);
                 throttleSlowQueueLimit = dafileSrvInstance->getPropInt("@throttleSlowQueueLimit", throttleSlowQueueLimit);
+
+                authorizedOnly = dafileSrvInstance->getPropBool("@authorizedOnly", authorizedOnly);
             }
         }
+        keyPairInfo = env->queryPropTree("EnvSettings/Keys");
     }
 
     // these should really be in env, but currently they are not ...
@@ -771,7 +778,7 @@ int main(int argc,char **argv)
 
     PROGLOG("Version: %s", verstring);
     PROGLOG("Authentication:%s required",requireauthenticate?"":" not");
-    server.setown(createRemoteFileServer(maxThreads, maxThreadsDelayMs, maxAsyncCopy));
+    server.setown(createRemoteFileServer(maxThreads, maxThreadsDelayMs, maxAsyncCopy, authorizedOnly, keyPairInfo));
     server->setThrottle(ThrottleStd, parallelRequestLimit, throttleDelayMs, throttleCPULimit);
     server->setThrottle(ThrottleSlow, parallelSlowRequestLimit, throttleSlowDelayMs, throttleSlowCPULimit);
     class CPerfHook : public CSimpleInterfaceOf<IPerfMonHook>