Skip to content

Commit

Permalink
SNOW-715510: Token cache for libsnowflakeclient (#773)
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-jszczerbinski authored Feb 5, 2025
1 parent fedf1f9 commit 954c938
Show file tree
Hide file tree
Showing 24 changed files with 1,758 additions and 11 deletions.
25 changes: 23 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ set(SOURCE_FILES
include/snowflake/logger.h
include/snowflake/version.h
include/snowflake/platform.h
include/snowflake/secure_storage.h
lib/client.c
lib/constants.h
lib/cJSON.h
Expand Down Expand Up @@ -63,7 +64,9 @@ set(SOURCE_FILES
lib/chunk_downloader.h
lib/chunk_downloader.c
lib/mock_http_perform.h
lib/http_perform.c)
lib/http_perform.c
cpp/lib/CacheFile.cpp
)

set (SOURCE_FILES_PUT_GET
cpp/EncryptionProvider.cpp
Expand Down Expand Up @@ -150,6 +153,7 @@ set(SOURCE_FILES_CPP_WRAPPER
include/snowflake/CurlDesc.hpp
include/snowflake/CurlDescPool.hpp
include/snowflake/BindUploader.hpp
include/snowflake/SecureStorage.hpp
cpp/lib/Exceptions.cpp
cpp/lib/Connection.cpp
cpp/lib/Statement.cpp
Expand All @@ -173,14 +177,27 @@ set(SOURCE_FILES_CPP_WRAPPER
cpp/lib/BindUploader.cpp
cpp/lib/ClientBindUploader.hpp
cpp/lib/ClientBindUploader.cpp
cpp/lib/CacheFile.cpp
cpp/lib/CacheFile.hpp
cpp/platform/secure_storage.cpp
cpp/platform/SecureStorage.cpp
cpp/platform/SecureStorageApple.cpp
cpp/platform/SecureStorageLinux.cpp
cpp/platform/SecureStorageWin.cpp
cpp/platform/FileLock.cpp
cpp/platform/FileLock.hpp
cpp/util/SnowflakeCommon.cpp
cpp/util/SFURL.cpp
cpp/util/CurlDesc.cpp
cpp/util/CurlDescPool.cpp
cpp/util/Sha256.cpp
cpp/util/Sha256.hpp
cpp/platform/SecureStorage.cpp
lib/result_set.h
lib/query_context_cache.h
lib/curl_desc_pool.h
lib/authenticator.h)
lib/authenticator.h
)

if (UNIX)
if (LINUX)
Expand Down Expand Up @@ -284,6 +301,7 @@ if (WIN32)
find_library(BOOST_REGEX_LIB boost_regex-vc140-mt-gd.lib PATHS deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/boost/lib/ REQUIRED NO_DEFAULT_PATH)
find_library(BOOST_SYSTEM_LIB boost_system-vc140-mt-gd.lib PATHS deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/boost/lib/ REQUIRED NO_DEFAULT_PATH)
else()
message(deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/boost/lib/)
find_library(BOOST_FILESYSTEM_LIB boost_filesystem-vc140-mt.lib PATHS deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/boost/lib/ REQUIRED NO_DEFAULT_PATH)
find_library(BOOST_REGEX_LIB boost_regex-vc140-mt.lib PATHS deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/boost/lib/ REQUIRED NO_DEFAULT_PATH)
find_library(BOOST_SYSTEM_LIB boost_system-vc140-mt.lib PATHS deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/boost/lib/ REQUIRED NO_DEFAULT_PATH)
Expand Down Expand Up @@ -339,6 +357,7 @@ if (LINUX)
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/azure/include
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/cmocka/include
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/uuid/include
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/picojson/include
include
lib)
endif()
Expand All @@ -354,6 +373,7 @@ if (APPLE)
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/aws/include
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/azure/include
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/cmocka/include
deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/picojson/include
include
lib)
endif()
Expand All @@ -368,6 +388,7 @@ if (WIN32)
deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/aws/include
deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/azure/include
deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/cmocka/include
deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/picojson/include
include
lib)
if (CMAKE_SIZEOF_VOID_P EQUAL 8)
Expand Down
262 changes: 262 additions & 0 deletions cpp/lib/CacheFile.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
/*
* File: CacheFile.cpp *
* Copyright (c) 2025 Snowflake Computing
*/

#include "CacheFile.hpp"

#if defined(__linux__) || defined(__APPLE__)
#include <sys/stat.h>
#endif

#include <fstream>
#include <string>
#include <sstream>

#include <picojson.h>
#include <boost/filesystem.hpp>

#include "snowflake/platform.h"
#include "../logger/SFLogger.hpp"
#include "../util/Sha256.hpp"

namespace {
using namespace Snowflake::Client;
const char* CREDENTIAL_FILE_NAME = "credential_cache_v1.json";

bool mkdirIfNotExists(const std::string& dir)
{
int result = sf_mkdir(dir.c_str());
if (result == 0)
{
CXX_LOG_DEBUG("Created %s directory.", dir.c_str());
return true;
}

if (errno == EEXIST)
{
CXX_LOG_TRACE("Directory %s already exists.", dir.c_str());
return true;
}

CXX_LOG_ERROR("Failed to create %s directory. Error: %d", dir.c_str(), errno);
return false;

}

boost::optional<std::string> getEnv(const std::string& envVar)
{
char *root = getenv(envVar.c_str());

if (root == nullptr)
{
return {};
}

return std::string(root);
}

void ensureObject(picojson::value &val)
{
if (!val.is<picojson::object>())
{
val = picojson::value(picojson::object());
}
}

picojson::object& getTokens(picojson::value& cache)
{
ensureObject(cache);
auto &obj = cache.get<picojson::object>();
auto pair = obj.emplace("tokens", picojson::value(picojson::object()));
auto& tokens = pair.first->second;
ensureObject(tokens);
return tokens.get<picojson::object>();
}

#if defined(__linux__) || defined(__APPLE__)
bool ensurePermissions(const std::string& path, mode_t mode)
{
if (chmod(path.c_str(), mode) == -1)
{
CXX_LOG_ERROR("Cannot ensure permissions. chmod(%s, %o) failed with errno=%d", path.c_str(), mode, errno);
return false;
}

return true;
}
#else
bool ensurePermissions(const std::string& path, unsigned mode)
{
CXX_LOG_ERROR("Cannot ensure permissions on current platform");
return false;
}
#endif
}

namespace Snowflake {

namespace Client {
boost::optional<std::string> getCacheDir(const std::string& envVar, const std::vector<std::string>& subPathSegments)
{
#ifdef __linux__
auto envVarValueOpt = getEnv(envVar);
if (!envVarValueOpt)
{
return {};
}

const std::string& envVarValue = envVarValueOpt.get();

struct stat s = {};
int err = stat(envVarValue.c_str(), &s);

if (err != 0)
{
CXX_LOG_INFO("Failed to stat %s=%s, errno=%d. Skipping it in cache file location lookup.", envVar.c_str(), envVarValue.c_str(), errno);
return {};
}

if (!S_ISDIR(s.st_mode))
{
CXX_LOG_INFO("%s=%s is not a directory. Skipping it in cache file location lookup.", envVar.c_str(), envVarValue.c_str());
return {};
}

auto cacheDir = envVarValue;
for (const auto& segment: subPathSegments)
{
cacheDir.append(PATH_SEP + segment);
if (!mkdirIfNotExists(cacheDir))
{
CXX_LOG_INFO("Could not create cache dir=%s. Skipping it in cache file location lookup.", cacheDir.c_str());
return {};
}
}

if (!subPathSegments.empty())
{
err = stat(cacheDir.c_str(), &s);
if (err != 0)
{
CXX_LOG_INFO("Failed to stat %s, errno=%d. Skipping it in cache file location lookup.", cacheDir.c_str(), errno);
return {};
}
}

if (s.st_uid != geteuid())
{
CXX_LOG_INFO("%s=%s is not owned by current user. Skipping it in cache file location lookup.", envVar.c_str(), envVarValue.c_str());
return {};
}

unsigned permissions = s.st_mode & 0777;
if (permissions != 0700)
{
CXX_LOG_INFO("Incorrect permissions=%o for cache dir %s. Changing permissions to 700.", permissions, cacheDir.c_str())
if (chmod(cacheDir.c_str(), 0700) != 0)
{
CXX_LOG_WARN("Failed to change permissions for a cache dir %s, errno=%d. Skipping it in cache file location lookup.", cacheDir.c_str(), errno);
return {};
}
}
return cacheDir;
#else
CXX_LOG_FATAL("Using NOOP implementation. This function is implemented only for linux.");
return {};
#endif
}

boost::optional<std::string> getCredentialFilePath()
{
std::vector<std::function<boost::optional<std::string>()>> lookupFunctions =
{
[]() { return getCacheDir("SF_TEMPORARY_CREDENTIAL_CACHE_DIR", {}); },
#ifdef __linux__
[](){ return getCacheDir("XDG_CACHE_HOME", {"snowflake"}); },
#endif
[](){ return getCacheDir("HOME", {".cache", "snowflake"}); },
};

for (const auto& lf: lookupFunctions) {
boost::optional<std::string> directory = lf();
if (directory)
{
auto path = directory.get() + PATH_SEP + CREDENTIAL_FILE_NAME;
CXX_LOG_TRACE("Successfully found credential file path=%s", path.c_str());
return path;
}
}

return {};
};

std::string readFile(const std::string &path, picojson::value &result) {
if (!boost::filesystem::exists(path))
{
result = picojson::value(picojson::object());
return {};
}

std::ifstream cacheFile(path);
if (!cacheFile.is_open())
{
return "Failed to open the file(path=" + path + ")";
}

std::string error = picojson::parse(result, cacheFile);
if (!error.empty())
{
return "Failed to parse the file: " + error;
}
return {};
}

std::string writeFile(const std::string &path, const picojson::value &result) {
std::ofstream cacheFile(path, std::ios_base::trunc);
if (!cacheFile.is_open())
{
return "Failed to open the file";
}

if (!ensurePermissions(path, 0600))
{
return "Cannot ensure correct permissions on a file";
}

cacheFile << result.serialize(true);
return {};
}

void cacheFileUpdate(picojson::value &cache, const std::string &key, const std::string &credential)
{
picojson::object& tokens = getTokens(cache);
tokens.emplace(key, credential);
}

void cacheFileRemove(picojson::value &cache, const std::string &key)
{
picojson::object& tokens = getTokens(cache);
tokens.erase(key);
}

boost::optional<std::string> cacheFileGet(picojson::value &cache, const std::string &key) {
picojson::object& tokens = getTokens(cache);
auto it = tokens.find(key);

if (it == tokens.end())
{
return {};
}

if (!it->second.is<std::string>())
{
return {};
}

return it->second.get<std::string>();
}

}

}
32 changes: 32 additions & 0 deletions cpp/lib/CacheFile.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#ifndef SNOWFLAKECLIENT_CACHEFILE_HPP
#define SNOWFLAKECLIENT_CACHEFILE_HPP

#include <string>
#include <fstream>

#include <boost/optional.hpp>
#include <picojson.h>

namespace Snowflake {

namespace Client {

boost::optional<std::string> getCacheDir(const std::string& envVar, const std::vector<std::string>& subPathSegments);

boost::optional<std::string> getCredentialFilePath();

std::string readFile(const std::string &path, picojson::value &result);

std::string writeFile(const std::string &path, const picojson::value &result);

void cacheFileUpdate(picojson::value &cache, const std::string &key, const std::string &credential);

void cacheFileRemove(picojson::value &cache, const std::string &key);

boost::optional<std::string> cacheFileGet(picojson::value &cache, const std::string &key);

}

}

#endif // SNOWFLAKECLIENT_CACHEFILE_HPP
Loading

0 comments on commit 954c938

Please sign in to comment.