Skip to content

Commit 905b3ff

Browse files
committed
Added convcenience method to save SSH related data to the file system.
Added test coverage for new method. Updated examples to use the new convenience method.
1 parent 25e8a63 commit 905b3ff

7 files changed

Lines changed: 206 additions & 75 deletions

File tree

examples/ssh_certificates/get_cert_ssh.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import string
2323
from os import environ
2424

25-
from vcert import venafi_connection, Authentication, SCOPE_SSH, generate_ssh_keypair, SSHCertRequest
25+
from vcert import venafi_connection, Authentication, SCOPE_SSH, SSHKeyPair, SSHCertRequest, write_ssh_files
2626

2727
logging.basicConfig(level=logging.INFO)
2828
logging.getLogger("urllib3").setLevel(logging.ERROR)
@@ -55,10 +55,11 @@ def main():
5555
# IMPORTANT: Save the private key on a secure location and do not share it with anyone.
5656
# There is no way to decrypt the certificates generated with the public key
5757
# without the corresponding private key
58-
pub_key, priv_key = generate_ssh_keypair(key_size=4096, passphrase="foobar")
58+
ssh_kp = SSHKeyPair()
59+
ssh_kp.generate(key_size=4096, passphrase="foobar")
5960
# The path to the SSH CA in the TPP instance
6061
# This is a placeholder. Make sure an SSH CA already exists on your TPP instance
61-
cadn = "\\VED\\Certificate Authority\\SSH\\Templates\\open-source-test-cit"
62+
cadn = "\\VED\\Certificate Authority\\SSH\\Templates\\my-ca"
6263
# The id of the SSH certificate
6364
key_id = "vcert-python-%s" % random_word(12)
6465

@@ -71,14 +72,18 @@ def main():
7172
"permit-pty": ""
7273
}
7374
# Include the locally-generated public key. If not set, the server will generate one for the certificate
74-
request.public_key_data = pub_key
75+
request.public_key_data = ssh_kp.public_key()
7576

7677
# Request the certificate from TPP instance
7778
success = connector.request_ssh_cert(request)
7879
if success:
7980
# Retrieve the certificate from TPP instance
8081
response = connector.retrieve_ssh_cert(request)
81-
pass
82+
# Save the certificate to a file
83+
# The private and public key are optional values.
84+
write_ssh_files("/path/to/ssh/cert/folder", response.certificate_details.key_id, response.certificate_data,
85+
ssh_kp.private_key(),
86+
ssh_kp.public_key())
8287

8388

8489
def random_word(length):

examples/ssh_certificates/get_cert_ssh_service.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import string
2323
from os import environ
2424

25-
from vcert import venafi_connection, Authentication, SCOPE_SSH, SSHCertRequest
25+
from vcert import venafi_connection, Authentication, SCOPE_SSH, SSHCertRequest, write_ssh_files
2626

2727
logging.basicConfig(level=logging.INFO)
2828
logging.getLogger("urllib3").setLevel(logging.ERROR)
@@ -52,7 +52,7 @@ def main():
5252
connector.get_access_token(auth)
5353

5454
# The path to the SSH CA in the TPP instance
55-
cadn = "\\VED\\Certificate Authority\\SSH\\Templates\\open-source-test-cit"
55+
cadn = "\\VED\\Certificate Authority\\SSH\\Templates\\my-ca"
5656
# The id of the SSH certificate
5757
key_id = "vcert-python-%s" % random_word(12)
5858

@@ -74,7 +74,10 @@ def main():
7474
request.private_key_passphrase = "foobar"
7575
# Retrieve the certificate from TPP instance
7676
response = connector.retrieve_ssh_cert(request)
77-
pass
77+
# Save the certificate, private and public key to files
78+
write_ssh_files("/path/to/ssh/cert/folder", response.certificate_details.key_id, response.certificate_data,
79+
response.private_key_data,
80+
response.public_key_data)
7881

7982

8083
def random_word(length):

tests/assets.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,4 +426,65 @@
426426
mFFkM+qC8o8G9zXULydK1DBnWJqBfHGoFu9+JkEcykPsANjHRoG9
427427
-----END RSA PRIVATE KEY-----
428428
429-
"""
429+
"""
430+
431+
SSH_CERT_DATA = "ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAggY7TIQpWzhBFShMZLR0mh2/" \
432+
"5aK5S3xxNUVaK3EP0XI0AAAADAQABAAABgQDFCm9cWkABjag9XaoFoV50U7EK+v69jm9qa6aY53XWYY1V9E8vOk21zQD2+ROC623" \
433+
"yrVv59JanFSmgrFfQpDhUKwrMAqLuzloSYtaKH7Ft3A9uIx78D862zFykW0oTs7uR2kG2DIGWupAL469CHv4S0ljtZ5pYyU3m5Pm" \
434+
"EwaFn+lndfSe+aju1qhtbNrIib/F96tg/0f8KcDxU+ilb2kl30vaNa/zvga/x+kJKE1A3VSjy97n7uSJji7fcN8WnEGOsDEL4dZQ" \
435+
"RIzCXx0aKlMr1bkAxQ0h5QK5rE1RPhIlfckxrsOw9yEoXMnU7qv/IGZ9EaIpxVhK5SlNGnFo0pTs5OE5FdlZvW3IBJzKci+gRD8Z" \
436+
"DHqk4Wf4zlS9whb/kz7XsaJBreIz0M8V50FNbpn09tHUNYkKvynElO8BC5X1YkoAoRwysk3WrTCo7UJQ8Q+whle0LUCXcdJqs0NN" \
437+
"O4kKLwA/6IrCJirhZi1RGp3CDIEfl5wUs62HVLeB94h/3jF353sgvoJOo4AAAAAEAAAAfdmNlcnQtZ28tMTYyNjgwNTg2OC1zeHp" \
438+
"zU1NIQ2VydAAAABAAAAAFYWxpY2UAAAADYm9iAAAAAGES4REAAAAAYRMZUQAAACIAAAAOc291cmNlLWFkZHJlc3MAAAAMAAAACHR" \
439+
"lc3QuY29tAAAAKAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAACFwAAAAdzc2gtcnNhAAAAAwE" \
440+
"AAQAAAgEAtP5TdyYaXn9EsihnCRuXOeAcplCrr7G4eUmpgxI/iOo8PyO3LcfeeSwgecEcZZCEZRZGLl0i0GrHS4Y5wxz14lvJ4/s" \
441+
"w7hwdUC4+BNh5kCscC5YhtUU9SWliaSCutz1NvGsRoBPk/6R/aScuBpEepggbL9ol7ObICuvGyvoSvFoZbYCD8kpCHxbnBNxLCuI" \
442+
"Rx3DAnsMyfT9V/jdj3+lxJSYDMtWhQ01f4EjON4esrz/gMmU/Yl9R1qSK6TM/T0r2RYNEq3f9XfywLDBry+gEKrvkwlhBPkPSC+1" \
443+
"onx66rSXUqsHhK09bn8FrVGVDFd4Cl/xhdLld8BHbxV6dI7qj0Fop7P/v/d7Iqvd1LExOoqTRqM6ZhmFa147ocksTxpxJDe+XvVk" \
444+
"fW8H6/89HqcERGrlmQTxGmh6W9JbuDbHJoTmrF6igcwDRBY5BrU0fYLXTqQenA60YqJD1iO6Cde2WsWa/HyoAdF2O837oRVNze9V" \
445+
"rHD/tKypN+w0aF7gglG0KVJxu29B34mspx2xSUuafqLeSq5j3EXZbeapDoCkS+7U8yXDQSD5P8sB/9XvyCkbxMv6H2yKhWYooGYX" \
446+
"6F/s3nHLd5wooRwLN5DVDd74WrVoSnuHfQEhUrq3iHWALW0Jl54yJWdVqVBCY6JCT0GnvzZRNKMP8G255qVqetSk7/qkAAAIUAAA" \
447+
"ADHJzYS1zaGEyLTUxMgAAAgB43MnV/s1iyt0KPnfyAqvvvX3xUlO83gKJQe3vgGnGwGfo0hS3Rg53BCUjGTVUGvHyH/Lxj4F7wqA" \
448+
"WuK0Fs3EzsIIIKD/pyJPWaDSgj2yPP9ElNBCqhwOYXgvDyPSKjsxjDqDhINAna8cdQdD+wDrckc/SX/Jt7DgBrD8WRy3Otr+FKnA" \
449+
"zJeEZe4MwSa4WjGcMnfwH4hStI6hwuuX3i486EYyrC3Kl6t2TUUF3YosOu09eIAMDDw5PDtp8Uhmzz+n1rQNyTn6L4vq2j9P01I8" \
450+
"ynmOwq1+DgyGb4bcMDRStNIiBb1qhLAW+nxeL2Mw68w4LkGh0UT0T+ivGbEWuZWD2VniPLZjqjWCHdpdWW8SW27UQF9ypsjxiIVF" \
451+
"kALulljvFIztOR7ZN5bFgxtMTHJb5mHogjWCzcs0xY1olJuYdNsGkoOoSQnkE/M4JmVoDOjBwSQLkSx54nLJPgxf10ZCiEqIk/dq" \
452+
"R4jBmv39skpb1DIo0Jgm4fE1czNPmYJldYhSbMud1RNyyFAmLmkVxcqV0dPQ50aFHOl/LG2CpOn9l2qYvhEs9iXEpua5sWR/R31O" \
453+
"q6ahPWfA4Wfn2tKtOIgJaZTOLgfys4QUhbRpZ8btV79hjrOlPzryg5pflQe8dx63Iut9HR+SOZzxnE2pFYJSgcJO8prnlIfdNq5B" \
454+
"xPpHQqA== vcert-go-1626805868-sxzsSSHCert"
455+
456+
SSH_PUBLIC_KEY = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDFCm9cWkABjag9XaoFoV50U7EK+v69jm9qa6aY53XWYY1V9E8vOk21zQD2+RO" \
457+
"C623yrVv59JanFSmgrFfQpDhUKwrMAqLuzloSYtaKH7Ft3A9uIx78D862zFykW0oTs7uR2kG2DIGWupAL469CHv4S0ljtZ5pYyU" \
458+
"3m5PmEwaFn+lndfSe+aju1qhtbNrIib/F96tg/0f8KcDxU+ilb2kl30vaNa/zvga/x+kJKE1A3VSjy97n7uSJji7fcN8WnEGOsD" \
459+
"EL4dZQRIzCXx0aKlMr1bkAxQ0h5QK5rE1RPhIlfckxrsOw9yEoXMnU7qv/IGZ9EaIpxVhK5SlNGnFo0pTs5OE5FdlZvW3IBJzKc" \
460+
"i+gRD8ZDHqk4Wf4zlS9whb/kz7XsaJBreIz0M8V50FNbpn09tHUNYkKvynElO8BC5X1YkoAoRwysk3WrTCo7UJQ8Q+whle0LUCX" \
461+
"cdJqs0NNO4kKLwA/6IrCJirhZi1RGp3CDIEfl5wUs62HVLeB94h/3jF0= vcert-go-1626805868-sxzsSSHCert"
462+
463+
SSH_PRIVATE_KEY = "-----BEGIN OPENSSH PRIVATE KEY-----\r\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwA" \
464+
"AAAdz\r\nc2gtcnNhAAAAAwEAAQAAAYEAxQpvXFpAAY2oPV2qBaFedFOxCvr+vY5vamummOd1\r\n1mGNVfRPLzpNtc0A9vkTg" \
465+
"utt8q1b+fSWpxUpoKxX0KQ4VCsKzAKi7s5aEmLWih+x\r\nbdwPbiMe/A/OtsxcpFtKE7O7kdpBtgyBlrqQC+OvQh7+EtJY7We" \
466+
"aWMlN5uT5hMGh\r\nZ/pZ3X0nvmo7taobWzayIm/xferYP9H/CnA8VPopW9pJd9L2jWv874Gv8fpCShNQ\r\nN1Uo8ve5+7kiY" \
467+
"4u33DfFpxBjrAxC+HWUESMwl8dGipTK9W5AMUNIeUCuaxNUT4SJ\r\nX3JMa7DsPchKFzJ1O6r/yBmfRGiKcVYSuUpTRpxaNKU" \
468+
"7OThORXZWb1tyAScynIvo\r\nEQ/GQx6pOFn+M5UvcIW/5M+17GiQa3iM9DPFedBTW6Z9PbR1DWJCr8pxJTvAQuV9\r\nWJKAK" \
469+
"EcMrJN1q0wqO1CUPEPsIZXtC1Al3HSarNDTTuJCi8AP+iKwiYq4WYtURqdw\r\ngyBH5ecFLOth1S3gfeIf94xdAAAFeG7jGyt" \
470+
"u4xsrAAAAB3NzaC1yc2EAAAGBAMUK\r\nb1xaQAGNqD1dqgWhXnRTsQr6/r2Ob2prppjnddZhjVX0Ty86TbXNAPb5E4LrbfKt" \
471+
"\r\nW/n0lqcVKaCsV9CkOFQrCswCou7OWhJi1oofsW3cD24jHvwPzrbMXKRbShOzu5Ha\r\nQbYMgZa6kAvjr0Ie/hLSWO1nml" \
472+
"jJTebk+YTBoWf6Wd19J75qO7WqG1s2siJv8X3q\r\n2D/R/wpwPFT6KVvaSXfS9o1r/O+Br/H6QkoTUDdVKPL3ufu5ImOLt9w3" \
473+
"xacQY6wM\r\nQvh1lBEjMJfHRoqUyvVuQDFDSHlArmsTVE+EiV9yTGuw7D3IShcydTuq/8gZn0Ro\r\ninFWErlKU0acWjSlOz" \
474+
"k4TkV2Vm9bcgEnMpyL6BEPxkMeqThZ/jOVL3CFv+TPtexo\r\nkGt4jPQzxXnQU1umfT20dQ1iQq/KcSU7wELlfViSgChHDKyT" \
475+
"datMKjtQlDxD7CGV\r\n7QtQJdx0mqzQ007iQovAD/oisImKuFmLVEancIMgR+XnBSzrYdUt4H3iH/eMXQAA\r\nAAMBAAEAAA" \
476+
"GBAI0npZFOYg360ixr/hIcgRLqpakNGBLph+2AAxAEuIkEx4BtDZDy\r\nvprfqrJCyvR9/fzcrkhJLOezJGgjLqGcE3JQh2KQ" \
477+
"/PIAx8vmHHVEsRHXlQI8jMTb\r\n+iVAD5n6f/3PQy8AYRSuruMw8WJjELlYhIEtSWGBV7QFOhaPsCSnph5b3abrMt6Z\r\nMf" \
478+
"85RnG4pDJBaYngBFzr7j4nG0FxGNHhdcIuUkwLsV5O1KGPu49feJa7HiO4zfaD\r\nsRwJCoTrO2AsP0f3StiKbyGZt98WHXqS" \
479+
"9DDdLKBB8O0YSm1uCs0mVn1CkWJMseg+\r\naCa6m5MmOmIU7wFQV87/Up0S/ym+Q9pOERgYn50NCJsY9S/1ORgxo9/fsj1KSb" \
480+
"xK\r\nae2b3brRcueJ6rtQsNlm9wjYcYSzDiwl8AQhGEx8lF0RwnMF7AKgkpMKyFfQ2Tbu\r\n9zqlXSoSUI9aAbItVjDfM1Kd" \
481+
"tuRHSs7CRcqRVC+1D2xQ/Knu8fje84hCQIk22iyr\r\nBOd+1kLGii1izQAAAMEApzivPz6hZHAfXmCf3EUxTXFxOekOCbpmkv" \
482+
"hE3m+6THTH\r\nIa034I8S2D79sILL10VcevRjbHmYcwlHNIR2U6Wvl8asq4YKk9P2Yoqx3XZMsjOO\r\ncNCPPs9voSGzFDyx" \
483+
"TfSZ91zIy1YOsuhvq9eXCdyusx4vq2TqpfhXCe4AK+P/pyp/\r\nA6KGrGPT8lxreVyQMWC5Nt/sEWuYGg02lMd9VC+sHMUKZi" \
484+
"9h6ENAPrLcUmkU3GvS\r\nksCH3bPwszdeL89+yCEeAAAAwQD1w+Nkx+dn69z4v8E2IUJG2g7wy1zRKkqix8Sa\r\noXig893l" \
485+
"0fJQC7Y9c0dTr0YwnEwkPjjU7cqi7nMS93CaRDhd7ad3IgR+3a5obB5m\r\nOxPWVR7HNOyqCcJDt9ADIkWV341Nzh2l1R8mmB" \
486+
"i4jbvPvU7xtCFLWCt5+Rfc0yFP\r\n1FHnUw3L6wuhKdlRY5jP5stRjz7zd2ChoSk7x11Azb4agAd+k1v6GsujD4tYaOvl\r\n" \
487+
"oOdJNU+X8erSqfTDKS/hxKYo+QcAAADBAM0/GAWL0JgiMHaNyoVuR11f+XOlBkQE\r\nkqfSVclk15zW3xWSWnxg0odmq0Vn6k" \
488+
"AMRHZAQ+2f/oRad7qbkv6KXi70TcaRDe1G\r\nyyeQC7bs147nudDwoO9Iq4V7sj1TyU1TOOk4t95S6sOo4+crd7kq+l1g5ed5" \
489+
"GRqv\r\nMKfg4Ae8NEiJLSbKcB4nDzFzk03JND6gJY8YiwQK6GYnBLEaPtuTIhb6XAUr4aSa\r\nw3elZMPNVE0KryHBahiIZq" \
490+
"vpeKD5f71qewAAAAAB\r\n-----END OPENSSH PRIVATE KEY-----\r\n"

tests/test_env.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
CLOUD_ENTRUST_CA_NAME = environ.get("CLOUD_ENTRUST_CA_NAME")
4040
CLOUD_DIGICERT_CA_NAME = environ.get("CLOUD_DIGICERT_CA_NAME")
4141

42-
SSH_CADN = environ.get("SSH_CADN")
42+
TPP_SSH_CADN = environ.get("TPP_SSH_CADN")
4343

4444
if RANDOM_DOMAIN and not isinstance(RANDOM_DOMAIN, text_type):
4545
RANDOM_DOMAIN = RANDOM_DOMAIN.decode()

tests/test_ssh.py

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515
# limitations under the License.
1616
#
1717
import logging
18+
import platform
19+
import re
1820
import unittest
1921

20-
from test_env import timestamp, TPP_TOKEN_URL, TPP_USER, TPP_PASSWORD, SSH_CADN
22+
from assets import SSH_CERT_DATA, SSH_PRIVATE_KEY, SSH_PUBLIC_KEY
23+
from test_env import timestamp, TPP_TOKEN_URL, TPP_USER, TPP_PASSWORD, TPP_SSH_CADN
2124
from vcert import CommonConnection, SSHCertRequest, TPPTokenConnection, Authentication, \
22-
SCOPE_SSH, generate_ssh_keypair
23-
from vcert.ssh_utils import SSHRetrieveResponse
25+
SCOPE_SSH, write_ssh_files
26+
from vcert.ssh_utils import SSHRetrieveResponse, SSHKeyPair
2427

2528
logging.basicConfig(level=logging.DEBUG)
2629
logger = logging.getLogger('vcert-test')
@@ -32,35 +35,63 @@
3235

3336
class TestTPPSSHCertificate(unittest.TestCase):
3437
def __init__(self, *args, **kwargs):
35-
self.tpp_conn = TPPTokenConnection(url=TPP_TOKEN_URL, http_request_kwargs={"verify": "/tmp/chain.pem"})
38+
self.tpp_conn = TPPTokenConnection(url=TPP_TOKEN_URL, http_request_kwargs={"verify": False})
3639
auth = Authentication(user=TPP_USER, password=TPP_PASSWORD, scope=SCOPE_SSH)
3740
self.tpp_conn.get_access_token(auth)
3841
super(TestTPPSSHCertificate, self).__init__(*args, **kwargs)
3942

4043
def test_enroll_local_generated_keypair(self):
41-
keypair = generate_ssh_keypair(key_size=4096, passphrase="foobar")
44+
keypair = SSHKeyPair()
45+
keypair.generate(key_size=4096, passphrase="foobar")
4246

43-
request = SSHCertRequest(cadn=SSH_CADN, key_id=_random_key_id())
47+
request = SSHCertRequest(cadn=TPP_SSH_CADN, key_id=_random_key_id())
4448
request.validity_period = "4h"
4549
request.source_addresses = ["test.com"]
46-
request.set_public_key_data(keypair.public_key)
50+
request.set_public_key_data(keypair.public_key())
4751
response = _enroll_ssh_cert(self.tpp_conn, request)
4852
self.assertTrue(response.private_key_data is None,
4953
SERVICE_GENERATED_NO_KEY_ERROR % ("Private", "not", request.key_id))
5054
self.assertTrue(response.public_key_data, SERVICE_GENERATED_NO_KEY_ERROR % ("Public", "", request.key_id))
5155
self.assertTrue(response.public_key_data == request.get_public_key_data(),
5256
"Public key on response does not match request.\nExpected: %s\nGot: %s"
5357
% (request.get_public_key_data(), response.public_key_data))
54-
self.assertTrue(response.cert_data, SSH_CERT_DATA_ERROR % request.key_id)
58+
self.assertTrue(response.certificate_data, SSH_CERT_DATA_ERROR % request.key_id)
5559

5660
def test_enroll_service_generated_keypair(self):
57-
request = SSHCertRequest(cadn=SSH_CADN, key_id=_random_key_id())
61+
request = SSHCertRequest(cadn=TPP_SSH_CADN, key_id=_random_key_id())
5862
request.validity_period = "4h"
5963
request.source_addresses = ["test.com"]
6064
response = _enroll_ssh_cert(self.tpp_conn, request)
6165
self.assertTrue(response.private_key_data, SERVICE_GENERATED_NO_KEY_ERROR % ("Private", "", request.key_id))
6266
self.assertTrue(response.public_key_data, SERVICE_GENERATED_NO_KEY_ERROR % ("Public", "", request.key_id))
63-
self.assertTrue(response.cert_data, SSH_CERT_DATA_ERROR % request.key_id)
67+
self.assertTrue(response.certificate_data, SSH_CERT_DATA_ERROR % request.key_id)
68+
69+
70+
class TestSSHUtils(unittest.TestCase):
71+
72+
def test_write_ssh_files(self):
73+
key_id = _random_key_id()
74+
normalized_name = re.sub(r"[^A-Za-z0-9]+", "_", key_id)
75+
full_path = "./" + normalized_name
76+
write_ssh_files("./", key_id, SSH_CERT_DATA, SSH_PRIVATE_KEY, SSH_PUBLIC_KEY)
77+
78+
err_msg = "%s serialization does not match expected value"
79+
80+
with open(full_path + "-cert.pub", "r") as cert_file:
81+
s_cert = cert_file.read()
82+
self.assertTrue(SSH_CERT_DATA == s_cert, err_msg % "SSH Certificate")
83+
84+
with open(full_path, "r") as priv_key_file:
85+
s_priv_key = priv_key_file.read()
86+
expected_priv_key = SSH_PRIVATE_KEY
87+
if platform.system() is not "Windows":
88+
expected_priv_key = expected_priv_key.replace("\r\n", "\n")
89+
90+
self.assertTrue(expected_priv_key == s_priv_key, err_msg % "SSH Private Key")
91+
92+
with open(full_path + ".pub", "r") as pub_key_file:
93+
s_pub_key = pub_key_file.read()
94+
self.assertTrue(SSH_PUBLIC_KEY == s_pub_key, err_msg % "SSH Public Key")
6495

6596

6697
def _enroll_ssh_cert(connector, request):

vcert/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from .connection_tpp_token import TPPTokenConnection
2222
from .connection_fake import FakeConnection
2323
from .pem import Certificate
24-
from .ssh_utils import SSHCertRequest, generate_ssh_keypair
24+
from .ssh_utils import SSHCertRequest, SSHKeyPair, write_ssh_files
2525

2626

2727
def Connection(url=None, token=None, user=None, password=None, fake=False, http_request_kwargs=None):

0 commit comments

Comments
 (0)