-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
gimre
committed
Mar 31, 2021
1 parent
c522946
commit 26d4a00
Showing
5 changed files
with
295 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,61 +1,79 @@ | ||
# symbol-node-configurator | ||
|
||
[![Build Status](https://travis-ci.com/nemtech/symbol-node-configurator.svg?branch=dev)](https://travis-ci.com/nemtech/symbol-node-configurator) | ||
|
||
This repository contains two helper tools: | ||
* votingkey.py - this is alternative voting key file generator | ||
* generate.py - actual catapult.server configuration generator, that will be described below | ||
|
||
## Prerequisites: | ||
|
||
python3 -m pip install -r requirements.txt | ||
|
||
Generator tool assumes that in working directory, there is directory called `certificates` | ||
that contains: | ||
* `ca.pubkey.pem` | ||
* `ca.crt.pem` | ||
* `node.full.crt.pem` | ||
* `node.crt.pem` | ||
* `node.key.pem` | ||
|
||
When running with `--harvesting` switch, generator tool additionally expects: | ||
* `private.harvesting.txt` and `private.vrf.txt`, containing respectively harvesting private key and vrf private key in hex OR | ||
* `private.harvesting.pem` and `private.vrf.pem` pem files, containing private keys, if pem files are password protected | ||
`--ask-pass` switch needs to be present | ||
|
||
When running with `--voting` switch, generator tool additionally expects voting key file: | ||
* `private_key_tree*.dat` - the file will be MOVED to destination directory, | ||
|
||
note: in case of `--voting` if destination directory contains file with given name, the index will be incremented, until "empty" one | ||
will be found | ||
|
||
## Examples: | ||
|
||
All examples assume the script is started from PARENT directory | ||
|
||
|
||
### Create configuration for api node | ||
|
||
python3 symbol-node-configurator/generator.py --mode api --output ../settings | ||
|
||
### Create configuration for harvesting peer node | ||
|
||
To create a configuration for harvesting node two files are needed, with keys in hex: | ||
* private.harvesting.txt | ||
* private.vrf.txt | ||
|
||
echo "C0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FF" > private.harvesting.txt | ||
echo "B007B007B007B007B007B007B007B007B007B007B007B007B007B007B007B007" > private.vrf.txt | ||
python3 symbol-node-configurator/generator.py --mode peer --output ../settings | ||
|
||
### Create configuration for harvesting and voting peer node | ||
|
||
echo "C0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FF" > private.harvesting.txt | ||
echo "B007B007B007B007B007B007B007B007B007B007B007B007B007B007B007B007" > private.vrf.txt | ||
python3 symbol-node-configurator/generator.py --mode peer --output ../settings --voting | ||
|
||
### Create configuration for harvesting and voting DUAL node | ||
|
||
echo "C0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FF" > private.harvesting.txt | ||
echo "B007B007B007B007B007B007B007B007B007B007B007B007B007B007B007B007" > private.vrf.txt | ||
python3 symbol-node-configurator/generator.py --mode dual --output ../settings --voting | ||
# symbol-node-configurator | ||
|
||
[![Build Status](https://travis-ci.com/nemtech/symbol-node-configurator.svg?branch=main)](https://travis-ci.com/nemtech/symbol-node-configurator) | ||
|
||
This repository contains a few tools: | ||
* pemtool.py - allows conversion from raw private key to (encrypted) PEM file (openssl format) | ||
* certtool.py - openssl wrapper for easy certificate generation | ||
* votingkey.py - this is alternative voting key file generator | ||
* generate.py - actual catapult.server configuration generator, that will be described below | ||
|
||
## Prerequisites: | ||
|
||
apt-get install python3 python3-pip openssl | ||
python3 -m pip install -r requirements.txt | ||
|
||
All commands below assume scripts are started from PARENT directory. | ||
|
||
## Pem tool | ||
|
||
Get help: | ||
python3 symbol-node-configurator/pemtool.py --help | ||
|
||
Convert private key in hex format to encrypted PEM file | ||
python3 symbol-node-configurator/pemtool.py --output ca.key.pem --ask-pass | ||
|
||
## Cert tool | ||
|
||
Get help: | ||
python3 symbol-node-configurator/certtool.py --help | ||
|
||
Create symbol server certificates using ca.key.pem | ||
python3 symbol-node-configurator/certtool.py --output certificates --ca ca.key.pem | ||
|
||
## Generate - config generator | ||
|
||
Generator tool assumes that in working directory, there is directory called `certificates` | ||
that contains: | ||
* `ca.pubkey.pem` | ||
* `ca.crt.pem` | ||
* `node.full.crt.pem` | ||
* `node.crt.pem` | ||
* `node.key.pem` | ||
|
||
When running with `--harvesting` switch, generator tool additionally expects: | ||
* `private.harvesting.txt` and `private.vrf.txt`, containing respectively harvesting private key and vrf private key in hex OR | ||
* `private.harvesting.pem` and `private.vrf.pem` pem files, containing private keys, if pem files are password protected | ||
`--ask-pass` switch needs to be present | ||
|
||
When running with `--voting` switch, generator tool additionally expects voting key file: | ||
* `private_key_tree*.dat` - the file will be MOVED to destination directory, | ||
|
||
note: in case of `--voting` if destination directory contains file with given name, the index will be incremented, until "empty" one | ||
will be found | ||
|
||
### Create configuration for api node | ||
|
||
python3 symbol-node-configurator/generator.py --mode api --output ../settings | ||
|
||
### Create configuration for harvesting peer node | ||
|
||
To create a configuration for harvesting node two files are needed, with keys in hex: | ||
* private.harvesting.txt | ||
* private.vrf.txt | ||
|
||
echo "C0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FF" > private.harvesting.txt | ||
echo "B007B007B007B007B007B007B007B007B007B007B007B007B007B007B007B007" > private.vrf.txt | ||
python3 symbol-node-configurator/generator.py --mode peer --output ../settings | ||
|
||
### Create configuration for harvesting and voting peer node | ||
|
||
echo "C0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FF" > private.harvesting.txt | ||
echo "B007B007B007B007B007B007B007B007B007B007B007B007B007B007B007B007" > private.vrf.txt | ||
python3 symbol-node-configurator/generator.py --mode peer --output ../settings --voting | ||
|
||
### Create configuration for harvesting and voting DUAL node | ||
|
||
echo "C0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FFEC0FF" > private.harvesting.txt | ||
echo "B007B007B007B007B007B007B007B007B007B007B007B007B007B007B007B007" > private.vrf.txt | ||
python3 symbol-node-configurator/generator.py --mode dual --output ../settings --voting |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import argparse | ||
import os | ||
import re | ||
import shutil | ||
import sys | ||
from pathlib import Path | ||
from subprocess import PIPE, STDOUT, Popen | ||
|
||
from symbolchain.core.CryptoTypes import PrivateKey | ||
from symbolchain.core.PrivateKeyStorage import PrivateKeyStorage | ||
from symbolchain.core.sym.KeyPair import KeyPair | ||
from zenlog import log | ||
|
||
|
||
def run_openssl(args, show_output=True): | ||
command_line = ['openssl'] + args | ||
process = Popen(command_line, stdout=PIPE, stderr=STDOUT) | ||
|
||
all_lines = [] | ||
for line_bin in iter(process.stdout.readline, b''): | ||
line = line_bin.decode('ascii') | ||
all_lines.append(line) | ||
|
||
if show_output: | ||
sys.stdout.write(line) | ||
sys.stdout.flush() | ||
|
||
process.wait() | ||
|
||
return all_lines | ||
|
||
|
||
def check_openssl_version(): | ||
version_output = ''.join(run_openssl(['version', '-v'], False)) | ||
match = re.match(r'^OpenSSL +([^ ]*) ', version_output) | ||
if not match or not match.group(1).startswith('1.1.1'): | ||
raise RuntimeError('{} requires openssl version >=1.1.1'.format(__file__)) | ||
|
||
|
||
def get_common_name(default_value, prompt): | ||
if default_value: | ||
return default_value | ||
|
||
return input('Enter {}: '.format(prompt)).strip() | ||
|
||
|
||
def prepare_ca_config(ca_pem_path, ca_cn): | ||
with open('ca.cnf', 'wt') as output_file: | ||
output_file.write('''[ca] | ||
default_ca = CA_default | ||
[CA_default] | ||
new_certs_dir = ./new_certs | ||
database = index.txt | ||
serial = serial.dat | ||
private_key = {private_key_path} | ||
certificate = ca.crt.pem | ||
policy = policy_catapult | ||
[policy_catapult] | ||
commonName = supplied | ||
[req] | ||
prompt = no | ||
distinguished_name = dn | ||
[dn] | ||
CN = {cn} | ||
'''.format(private_key_path=ca_pem_path, cn=ca_cn)) | ||
|
||
os.makedirs('new_certs') | ||
os.chmod('new_certs', 0o700) | ||
|
||
with open('index.txt', 'wt') as output_file: | ||
output_file.write('') | ||
|
||
|
||
def prepare_node_config(node_cn): | ||
with open('node.cnf', 'wt') as output_file: | ||
output_file.write('''[req] | ||
prompt = no | ||
distinguished_name = dn | ||
[dn] | ||
CN = {cn} | ||
'''.format(cn=node_cn)) | ||
|
||
|
||
def main(): | ||
parser = argparse.ArgumentParser(description='Cert generation tool', formatter_class=argparse.ArgumentDefaultsHelpFormatter) | ||
parser.add_argument('--output', help='certificates working directory (all files will be created inside)', default='certificates') | ||
parser.add_argument('--ca', help='path to key PEM file that will be used as a CA key', default='ca.key.pem') | ||
parser.add_argument('--name-ca', help='use provided name as CA CN (common name) - suggested: account name') | ||
parser.add_argument('--name-node', help='use provided name as node CN (common name) - suggested: node name, host or ip') | ||
parser.add_argument('--force', help='overwrite output directory if it already exists', action='store_true') | ||
args = parser.parse_args() | ||
|
||
filepath = Path(args.output) | ||
if filepath.exists(): | ||
if not args.force: | ||
raise FileExistsError('output directory ({}) already exists, use --force to overwrite'.format(filepath)) | ||
shutil.rmtree(filepath) | ||
|
||
check_openssl_version() | ||
|
||
# obtain full path prior to switching directory | ||
ca_path = Path(args.ca).absolute() | ||
|
||
os.makedirs(filepath) | ||
os.chdir(filepath) | ||
|
||
log.info('creating ca.pubkey.pem') | ||
run_openssl(['pkey', '-in', ca_path, '-pubout', '-out', 'ca.pubkey.pem']) | ||
|
||
log.info('creating random node.key.pem') | ||
node_key_pair = KeyPair(PrivateKey.random()) | ||
PrivateKeyStorage('.', None).save('node.key', node_key_pair.private_key) | ||
|
||
log.info('preparing configuration files') | ||
ca_cn = get_common_name(args.name_ca, 'CA common name') | ||
node_cn = get_common_name(args.name_node, 'node common name') | ||
prepare_ca_config(ca_path, ca_cn) | ||
prepare_node_config(node_cn) | ||
|
||
log.info('creating CA certificate') | ||
run_openssl([ | ||
'req', | ||
'-config', 'ca.cnf', | ||
'-keyform', 'PEM', | ||
'-key', ca_path, | ||
'-new', '-x509', | ||
'-days', '7300', | ||
'-out', 'ca.crt.pem']) | ||
|
||
# prepare node CSR | ||
run_openssl([ | ||
'req', | ||
'-config', 'node.cnf', | ||
'-key', 'node.key.pem', | ||
'-new', | ||
'-out', 'node.csr.pem']) | ||
|
||
log.info('signing node certificate') | ||
run_openssl(['rand', '-out', './serial.dat', '-hex', '19']) | ||
|
||
run_openssl([ | ||
'ca', | ||
'-config', 'ca.cnf', | ||
'-days', '375', | ||
'-notext', | ||
'-batch', | ||
'-in', 'node.csr.pem', | ||
'-out', 'node.crt.pem']) | ||
|
||
log.info('certificates generated in {} directory'.format(args.output)) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import argparse | ||
import getpass | ||
from binascii import unhexlify | ||
from pathlib import Path | ||
|
||
from symbolchain.core.CryptoTypes import PrivateKey | ||
from symbolchain.core.PrivateKeyStorage import PrivateKeyStorage | ||
|
||
|
||
def read_key(filename): | ||
return open(filename).read() | ||
|
||
|
||
def get_private_key(filename): | ||
private_key = getpass.getpass('Enter private key (in hex): ') if not filename else read_key(filename) | ||
return private_key.strip() | ||
|
||
|
||
def main(): | ||
parser = argparse.ArgumentParser(description='PEM tool') | ||
parser.add_argument('--output', help='output PEM key file', required=True) | ||
parser.add_argument('--input', help='input private key file (optional)') | ||
parser.add_argument('--ask-pass', help='encrypt PEM with a password (password prompt will be shown)', action='store_true') | ||
parser.add_argument('--force', help='overwrite output file if it already exists', action='store_true') | ||
args = parser.parse_args() | ||
|
||
output_name = args.output | ||
if output_name.endswith('.pem'): | ||
output_name = output_name[:-4] | ||
|
||
filepath = Path(output_name + '.pem') | ||
if filepath.exists() and not args.force: | ||
raise FileExistsError('output file ({}) already exists, use --force to overwrite'.format(filepath)) | ||
|
||
private_key = PrivateKey(unhexlify(get_private_key(args.input))) | ||
|
||
password = None | ||
if args.ask_pass: | ||
password = getpass.getpass('Provide {} password: '.format(filepath)) | ||
confirmation = getpass.getpass('Confirm {} password: '.format(filepath)) | ||
if confirmation != password: | ||
raise RuntimeError('Provided passwords do not match') | ||
|
||
storage = PrivateKeyStorage('.', password) | ||
storage.save(output_name, private_key) | ||
print('saved {}'.format(filepath)) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import argparse | ||
import asyncio | ||
import os | ||
|