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

manage permissions for keyring files and directories INDY-323 #232

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a52c425
helper class to manage keyrings and tests for it
andkononykhin Jun 26, 2017
743c9b6
fixed tests
andkononykhin Jun 26, 2017
1a0d906
allow save wallet by absolute path, updated tests
andkononykhin Jun 26, 2017
0d84cf6
default values for files and dirs permissions in keyring
andkononykhin Jun 27, 2017
79a4345
added logging about saved wallet
andkononykhin Jun 27, 2017
2103686
switch cli to use new API to save wallets, added test
andkononykhin Jun 27, 2017
3f7c036
removed saveGivenWallet API as obsolete
andkononykhin Jun 27, 2017
73114f3
added loadWallet api and tests
andkononykhin Jun 27, 2017
78e910e
swicthed wallet loading logic to new API, removed old one
andkononykhin Jun 27, 2017
7084e8a
added TODO comment
andkononykhin Jun 27, 2017
a4c811f
replace TypeError with more appropriate ValueError
andkononykhin Jun 27, 2017
c60fba1
Merge branch 'master' of https://github.com/evernym/plenum into bugfi…
andkononykhin Jul 3, 2017
7de3e76
improved style: variable name for imported class
andkononykhin Jul 3, 2017
c9bf63c
pathlib onstead of os for WalletStorageHelper
andkononykhin Jul 3, 2017
03bb25d
updated exception message check
andkononykhin Jul 4, 2017
e1c0f08
Merge branch 'master' of https://github.com/evernym/plenum into bugfi…
andkononykhin Jul 4, 2017
ffa30b9
Merge branch 'master' of https://github.com/evernym/plenum into bugfi…
andkononykhin Jul 4, 2017
edf9fdd
delayed walletSaver initialization
andkononykhin Jul 4, 2017
184e0aa
Merge branch 'master' into bugfix/keyringPermissions
andkononykhin Jul 5, 2017
c8bc6bb
Merge branch 'master' into bugfix/keyringPermissions
dhh1128 Jul 6, 2017
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
28 changes: 22 additions & 6 deletions plenum/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from plenum.cli.helper import getUtilGrams, getNodeGrams, getClientGrams, \
getAllGrams
from plenum.cli.phrase_word_completer import PhraseWordCompleter
from plenum.client.wallet import Wallet
from plenum.client.wallet import Wallet, WalletStorageHelper
from plenum.common.exceptions import NameAlreadyExists, KeysNotFoundException
from plenum.common.keygen_utils import learnKeysFromOthers, tellKeysToOthers, areKeysSetup
from plenum.common.plugin_helper import loadPlugins
Expand Down Expand Up @@ -75,8 +75,8 @@
from plenum.client.client import Client
from plenum.common.util import getMaxFailures, \
firstValue, randomString, bootstrapClientKeys, \
getFriendlyIdentifier, saveGivenWallet, \
normalizedWalletFileName, getWalletFilePath, getWalletByPath, \
getFriendlyIdentifier, \
normalizedWalletFileName, getWalletFilePath, \
getLastSavedWalletFileName
from stp_core.common.log import \
getlogger, Logger, getRAETLogFilePath, getRAETLogLevelFromConfig
Expand Down Expand Up @@ -179,6 +179,9 @@ def __init__(self, looper, basedirpath, nodeReg=None, cliNodeReg=None,
self._wallets = {} # type: Dict[str, Wallet]
self._activeWallet = None # type: Wallet
self.keyPairs = {}

self._walletSaver = None

'''
examples:
status
Expand Down Expand Up @@ -344,6 +347,16 @@ def config(self):
self._config = getConfig()
return self._config


@property
def walletSaver(self):
if self._walletSaver is None:
self._walletSaver = WalletStorageHelper(
self.getKeyringsBaseDir(),
dmode=self.config.KEYRING_DIR_MODE,
fmode=self.config.KEYRING_FILE_MODE)
return self._walletSaver

@property
def allGrams(self):
if not self._allGrams:
Expand Down Expand Up @@ -1375,6 +1388,7 @@ def _newWallet(self, walletName=None):

def _listKeyringsAction(self, matchedVars):
if matchedVars.get('list_krs') == 'list keyrings':
# TODO move file system related routine to WalletStorageHelper
keyringBaseDir = self.getKeyringsBaseDir()
contextDirPath = self.getContextBasedKeyringsBaseDir()
dirs_to_scan = self.getAllSubDirNamesForKeyrings()
Expand Down Expand Up @@ -1735,7 +1749,7 @@ def performValidationCheck(self, wallet, walletFilePath, override=False):

def restoreWalletByPath(self, walletFilePath, copyAs=None, override=False):
try:
wallet = getWalletByPath(walletFilePath)
wallet = self.walletSaver.loadWallet(walletFilePath)

if copyAs:
wallet.name=copyAs
Expand Down Expand Up @@ -1838,6 +1852,7 @@ def isAnyWalletFileExistsForGivenEnv(self, env):
return self.isAnyWalletFileExistsForGivenContext(pattern)

def isAnyWalletFileExistsForGivenContext(self, pattern):
# TODO move that to WalletStorageHelper
files = glob.glob(pattern)
if files:
return True
Expand Down Expand Up @@ -1871,8 +1886,9 @@ def performCompatibilityCheckBeforeSave(self):

def _saveActiveWalletInDir(self, contextDir, printMsgs=True):
try:
walletFilePath = saveGivenWallet(self._activeWallet,
self.walletFileName, contextDir)
walletFilePath = self.walletSaver.saveWallet(
self._activeWallet,
getWalletFilePath(contextDir, self.walletFileName))
if printMsgs:
self.print('Active keyring "{}" saved'.format(
self._activeWallet.name), newline=False)
Expand Down
132 changes: 132 additions & 0 deletions plenum/client/wallet.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from typing import Optional, Dict, NamedTuple
import os
import sys
import stat
from pathlib import Path

import jsonpickle
from libnacl import crypto_secretbox_open, randombytes, \
Expand Down Expand Up @@ -265,3 +269,131 @@ def _getIdData(self,
signer = self.idsToSigners.get(idr)
idData = self.ids.get(idr)
return IdData(signer, idData.lastReqId if idData else None)


class WalletStorageHelper:
"""Manages wallets

:param ``keyringsBaseDir``: keyrings base directory
:param dmode: (optional) permissions for directories inside
including the base one, default is 0700
:param fmode: (optional) permissions for files inside,
default is 0600
"""
def __init__(self, keyringsBaseDir, dmode=0o700, fmode=0o600):
self.dmode = dmode
self.fmode = fmode
self.keyringsBaseDir = keyringsBaseDir

@property
def keyringsBaseDir(self):
return str(self._baseDir)

@keyringsBaseDir.setter
def keyringsBaseDir(self, path):
self._baseDir = self._resolve(Path(path))

self._createDirIfNotExists(self._baseDir)
self._ensurePermissions(self._baseDir, self.dmode)

def _ensurePermissions(self, path, mode):
if stat.S_IMODE(path.stat().st_mode) != mode:
path.chmod(mode)

def _createDirIfNotExists(self, dpath):
if dpath.exists():
if not dpath.is_dir():
raise NotADirectoryError("{}".format(dpath))
else:
dpath.mkdir(parents=True, exist_ok=True)

def _resolve(self, path):
# ``strict`` argument appeared only version 3.6 of python
if sys.version_info < (3, 6, 0):
return Path(os.path.realpath(str(path)))
else:
return path.resolve(strict=False)

def _normalize(self, fpath):
return self._resolve(self._baseDir / fpath)

def encode(self, data):
return jsonpickle.encode(data, keys=True)

def decode(self, data):
return jsonpickle.decode(data, keys=True)

def saveWallet(self, wallet, fpath):
"""Save wallet into specified localtion.

Returns the canonical path for the ``fpath`` where ``wallet``
has been stored.

Error cases:
- ``fpath`` is not inside the keyrings base dir - ValueError raised
- directory part of ``fpath`` exists and it's not a directory -
NotADirectoryError raised
- ``fpath`` exists and it's a directory - IsADirectoryError raised

:param wallet: wallet to save
:param fpath: wallet file path, absolute or relative to
keyrings base dir
"""
if not fpath:
raise ValueError("empty path")

_fpath = self._normalize(fpath)
_dpath = _fpath.parent

try:
_dpath.relative_to(self._baseDir)
except ValueError:
raise ValueError(
"path {} is not is not relative to the keyrings {}".format(
fpath, self._baseDir))

self._createDirIfNotExists(_dpath)

# ensure permissions from the bottom of the directory hierarchy
while _dpath != self._baseDir:
self._ensurePermissions(_dpath, self.dmode)
_dpath = _dpath.parent

with _fpath.open("w") as wf:
self._ensurePermissions(_fpath, self.fmode)
encodedWallet = self.encode(wallet)
wf.write(encodedWallet)
logger.debug("stored wallet '{}' in {}".format(
wallet.name, _fpath))

return str(_fpath)

def loadWallet(self, fpath):
"""Load wallet from specified localtion.

Returns loaded wallet.

Error cases:
- ``fpath`` is not inside the keyrings base dir - ValueError raised
- ``fpath`` exists and it's a directory - IsADirectoryError raised

:param fpath: wallet file path, absolute or relative to
keyrings base dir
"""
if not fpath:
raise ValueError("empty path")

_fpath = self._normalize(fpath)
_dpath = _fpath.parent

try:
_dpath.relative_to(self._baseDir)
except ValueError:
raise ValueError(
"path {} is not is not relative to the keyrings {}".format(
fpath, self._baseDir))

with _fpath.open() as wf:
wallet = self.decode(wf.read())

return wallet
17 changes: 1 addition & 16 deletions plenum/common/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,23 +506,8 @@ def getWalletFilePath(basedir, walletFileName):
return os.path.join(basedir, walletFileName)


def saveGivenWallet(wallet, fileName, contextDir):
createDirIfNotExists(contextDir)
walletFilePath = getWalletFilePath(
contextDir, fileName)
with open(walletFilePath, "w+") as walletFile:
encodedWallet = encode(wallet, keys=True)
walletFile.write(encodedWallet)
return walletFilePath


def getWalletByPath(walletFilePath):
with open(walletFilePath) as walletFile:
wallet = decode(walletFile.read(), keys=True)
return wallet


def getLastSavedWalletFileName(dir):
# TODO move that to WalletStorageHelper
def getLastModifiedTime(file):
return os.stat(file).st_mtime_ns

Expand Down
5 changes: 4 additions & 1 deletion plenum/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@
CLIENT_MAX_RETRY_ACK = 5
CLIENT_MAX_RETRY_REPLY = 5


VIEW_CHANGE_TIMEOUT = 60 # seconds
MAX_CATCHUPS_DONE_DURING_VIEW_CHANGE = 5

# permissions for keyring dirs/files
KEYRING_DIR_MODE = 0o700 # drwx------
KEYRING_FILE_MODE = 0o600 # -rw-------
14 changes: 14 additions & 0 deletions plenum/test/cli/helper.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ast
import os
import stat
import re
import traceback
from tempfile import gettempdir, mkdtemp
Expand Down Expand Up @@ -586,6 +587,10 @@ def checkWalletFilePersisted(filePath):
assert os.path.exists(filePath)


def checkPermissions(path, mode):
assert stat.S_IMODE(os.stat(path).st_mode) == mode


def checkWalletRestored(cli, expectedWalletKeyName,
expectedIdentifiers):

Expand Down Expand Up @@ -627,6 +632,15 @@ def useAndAssertKeyring(do, name, expectedName=None, expectedMsgs=None):
)


def saveAndAssertKeyring(do, name, expectedName=None, expectedMsgs=None):
keyringName = expectedName or name
finalExpectedMsgs = expectedMsgs or \
['Active keyring "{}" saved'.format(keyringName)]
do('save keyring'.format(name),
expect=finalExpectedMsgs
)


def exitFromCli(do):
import pytest
with pytest.raises(cli.Exit):
Expand Down
25 changes: 25 additions & 0 deletions plenum/test/cli/test_save_wallet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import pytest

from plenum.common.util import getWalletFilePath
from plenum.test.cli.helper import createAndAssertNewCreation, \
checkWalletFilePersisted, checkPermissions, saveAndAssertKeyring


def createNewKey(do, cli, keyringName):
createAndAssertNewCreation(do, cli, keyringName)


def testSaveWallet(do, be, cli):
be(cli)
assert cli._activeWallet is None
createNewKey(do, cli, keyringName="Default")
saveAndAssertKeyring(do, "Default")
filePath = getWalletFilePath(
cli.getContextBasedKeyringsBaseDir(),
cli.walletFileName)

checkPermissions(cli.getKeyringsBaseDir(), cli.config.KEYRING_DIR_MODE)
checkPermissions(cli.getContextBasedKeyringsBaseDir(),
cli.config.KEYRING_DIR_MODE)
checkWalletFilePersisted(filePath)
checkPermissions(filePath, cli.config.KEYRING_FILE_MODE)
Loading