Skip to content

Commit

Permalink
Merge pull request #83 from Venafi/vaas_service_generated_csr
Browse files Browse the repository at this point in the history
Vaas service generated CSR
  • Loading branch information
rvelaVenafi authored Nov 23, 2021
2 parents 99eedf3 + 169ed0f commit e602434
Show file tree
Hide file tree
Showing 14 changed files with 573 additions and 122 deletions.
2 changes: 1 addition & 1 deletion examples/get_cert.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
# Copyright 2019 Venafi, Inc.
# Copyright 2022 Venafi, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
2 changes: 1 addition & 1 deletion examples/get_cert27.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python2
#
# Copyright 2019 Venafi, Inc.
# Copyright 2022 Venafi, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
# Copyright 2020 Venafi, Inc.
# Copyright 2022 Venafi, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
# Copyright 2019 Venafi, Inc.
# Copyright 2022 Venafi, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
# Copyright 2019 Venafi, Inc.
# Copyright 2022 Venafi, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
89 changes: 89 additions & 0 deletions examples/vaas/get_service_gen_cert_vaas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
#
# Copyright 2022 Venafi, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

from __future__ import print_function

from vcert import (CertificateRequest, venafi_connection, CSR_ORIGIN_SERVICE, CHAIN_OPTION_FIRST)
import string
import random
import logging
from os import environ

logging.basicConfig(level=logging.INFO)
logging.getLogger("urllib3").setLevel(logging.ERROR)


def main():
# Get credentials from environment variables
url = environ.get('VAAS_URL') # Optional, only use when connecting to a specific VaaS server
api_key = environ.get('VAAS_APIKEY')
zone = environ.get("VAAS_ZONE")

# Connection will be chosen automatically based on which arguments are passed.
# If api_key is passed, Venafi Cloud connection will be used.
# url attribute is no required when connecting to production VaaS platform
conn = venafi_connection(url=url, api_key=api_key)

# Build a Certificate request
request = CertificateRequest(common_name=random_word(10) + ".venafi.example.com")
# Set the request to use a service generated CSR
request.csr_origin = CSR_ORIGIN_SERVICE
# A password should be defined for the private key to be generated.
request.key_password = 'Foo.Bar.Pass.123!'
# Include some Subject Alternative Names
request.san_dns = ["www.dns.venafi.example.com", "ww1.dns.venafi.example.com"]
# Additional CSR attributes can be included:
request.organization = "Venafi, Inc."
request.organizational_unit = ["Product Management"]
request.locality = "Salt Lake City"
request.province = "Utah" # This is the same as state
request.country = "US"

# Specify ordering certificates in chain. Root can be CHAIN_OPTION_FIRST ("first")
# or CHAIN_OPTION_LAST ("last"). By default it is CHAIN_OPTION_LAST.
# request.chain_option = CHAIN_OPTION_FIRST
#
# To set Custom Fields for the certificate, specify an array of CustomField objects as name-value pairs
# request.custom_fields = [
# CustomField(name="Cost Center", value="ABC123"),
# CustomField(name="Environment", value="Production"),
# CustomField(name="Environment", value="Staging")
# ]
#
# Request the certificate.
conn.request_cert(request, zone)
# Wait for the certificate to be retrieved.
# This operation may take some time to return, as it waits until the certificate is ISSUED or it timeout.
# Timeout is 180s by default. Can be changed using:
# request.timeout = 300
cert = conn.retrieve_cert(request)

# Print the certificate
print(cert.full_chain)
# Save it into a file
f = open("./cert.pem", "w")
f.write(cert.full_chain)
f.close()


def random_word(length):
letters = string.ascii_lowercase
return ''.join(random.choice(letters) for i in range(length))


if __name__ == '__main__':
main()
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
# Copyright 2019 Venafi, Inc.
# Copyright 2022 Venafi, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ ipaddress;python_version<'3.3'
enum34;python_version<'3.4'
future
ruamel.yaml<0.17
pynacl>=1.4.0
34 changes: 32 additions & 2 deletions tests/test_vaas.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.x509.oid import NameOID

from test_env import CLOUD_ZONE, CLOUD_APIKEY, CLOUD_URL
from test_env import CLOUD_ZONE, CLOUD_APIKEY, CLOUD_URL, RANDOM_DOMAIN
from test_utils import random_word, enroll, renew, renew_by_thumbprint, renew_without_key_reuse, simple_enroll
from vcert import CloudConnection, KeyType, CertificateRequest, CustomField, logger
from vcert import CloudConnection, KeyType, CertificateRequest, CustomField, logger, CSR_ORIGIN_SERVICE

log = logger.get_child("test-vaas")

Expand Down Expand Up @@ -135,3 +136,32 @@ def test_cloud_enroll_valid_hours(self):
"Expected_delta: %s seconds."
% (expected_date.strftime(date_format), expiration_date.strftime(date_format),
delta.total_seconds()))

def test_cloud_enroll_service_generated_csr(self):
cn = random_word(10) + ".venafi.example.com"
password = 'FooBarPass123'

request = CertificateRequest(
common_name=cn,
key_password=password,
country='US'
)

request.san_dns = ["www.client.venafi.example.com", "ww1.client.venafi.example.com"]
request.csr_origin = CSR_ORIGIN_SERVICE

self.cloud_conn.request_cert(request, self.cloud_zone)
cert_object = self.cloud_conn.retrieve_cert(request)

cert = x509.load_pem_x509_certificate(cert_object.cert.encode(), default_backend())
assert isinstance(cert, x509.Certificate)
t1 = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)
t2 = [
x509.NameAttribute(
NameOID.COMMON_NAME, cn or RANDOM_DOMAIN
)
]
assert t1 == t2

output = cert_object.as_pkcs12('FooBarPass123')
log.info("PKCS12 created successfully for certificate with CN: %s" % cn)
117 changes: 52 additions & 65 deletions vcert/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,20 @@
from .ssh_utils import SSHCertRequest, SSHRetrieveResponse, SSHCATemplateRequest, SSHConfig
from .tpp_utils import IssuerHint

MIME_JSON = "application/json"
MIME_HTML = "text/html"
MIME_TEXT = "text/plain"
MIME_CSV = "text/csv"
MIME_ANY = "*/*"
LOCALHOST = "127.0.0.1"
MIME_JSON = 'application/json'
MIME_HTML = 'text/html'
MIME_TEXT = 'text/plain'
MIME_CSV = 'text/csv'
MIME_ANY = '*/*'
MIME_OCTET_STREAM = 'application/octet-stream'
LOCALHOST = '127.0.0.1'
DEFAULT_TIMEOUT = 180
CSR_ORIGIN_PROVIDED = "provided"
CSR_ORIGIN_LOCAL = "local"
CSR_ORIGIN_SERVICE = "service"
CHAIN_OPTION_FIRST = "first"
CHAIN_OPTION_LAST = "last"
CHAIN_OPTION_IGNORE = "ignore"
CSR_ORIGIN_PROVIDED = 'provided'
CSR_ORIGIN_LOCAL = 'local'
CSR_ORIGIN_SERVICE = 'service'
CHAIN_OPTION_FIRST = 'first'
CHAIN_OPTION_LAST = 'last'
CHAIN_OPTION_IGNORE = 'ignore'


class CertField:
Expand Down Expand Up @@ -221,33 +222,32 @@ def __str__(self):
return self.name


class RecommendedSettings:
def __init__(self, subject_o_value=None, subject_ou_value=None, subject_l_value=None, subject_st_value=None,
subject_c_value=None, key_type=None, key_reuse=None):
"""
:param str subject_o_value:
:param str subject_ou_value:
:param str subject_l_value:
:param str subject_st_value:
:param str subject_c_value:
:param KeyType key_type:
:param bool key_reuse:
"""
self.subjectOValue = subject_o_value
self.subjectOUValue = subject_ou_value
self.subjectLValue = subject_l_value
self.subjectSTValue = subject_st_value
self.subjectCValue = subject_c_value
self.keyType = key_type
self.keyReuse = key_reuse


class CertificateRequest:
def __init__(self, cert_id=None, san_dns=None, email_addresses="", ip_addresses=None, user_principal_names=None,
uniform_resource_identifiers=None, attributes=None, key_type=None, private_key=None, key_password=None,
csr=None, friendly_name=None, common_name=None, thumbprint=None, organization=None,
organizational_unit=None, country=None, province=None, locality=None, origin=None, custom_fields=None,
timeout=DEFAULT_TIMEOUT, csr_origin=CSR_ORIGIN_LOCAL, include_private_key=False, validity_hours=None,
def __init__(self, cert_id=None,
san_dns=None,
email_addresses="",
ip_addresses=None,
user_principal_names=None,
uniform_resource_identifiers=None,
attributes=None,
key_type=None,
private_key=None,
key_password=None,
csr=None,
friendly_name=None,
common_name=None,
thumbprint=None,
organization=None,
organizational_unit=None,
country=None,
province=None,
locality=None,
origin=None,
custom_fields=None,
timeout=DEFAULT_TIMEOUT,
csr_origin=CSR_ORIGIN_LOCAL,
include_private_key=False,
validity_hours=None,
issuer_hint=IssuerHint.DEFAULT):
"""
:param str cert_id: Certificate request id. Generating by server.
Expand All @@ -259,11 +259,16 @@ def __init__(self, cert_id=None, san_dns=None, email_addresses="", ip_addresses=
:param attributes:
:param KeyType key_type: Type of asymmetric cryptography algorithm. Default is RSA 2048.
:param asymmetric.PrivateKey private_key: String with pem encoded private key or asymmetric.PrivateKey
:param str key_password: Password for encrypted private key. Not supported at this moment.
:param str key_password: Password for encrypted private key.
:param str csr: Certificate Signing Request in pem format
:param str friendly_name: Name for certificate in the platform. If not specified common name will be used.
:param str common_name: Common name of certificate. Usually domain name.
:param str thumbprint: Certificate thumbprint. Can be used for identifying certificate on the platform.
:param organization:
:param organizational_unit:
:param country:
:param province:
:param locality:
:param str origin: application identifier
:param list[CustomField] custom_fields: list of custom fields values to be added to the certificate.
:param int timeout: Timeout for the certificate to be retrieved from server. Measured in seconds.
Expand Down Expand Up @@ -582,32 +587,6 @@ def __init__(self, user=None, password=None, access_token=None, refresh_token=No
self.state = state


class AppDetails:
def __init__(self, app_id=None, cit_map=None, company_id=None, name=None, description=None,
owner_ids_and_types=None, fq_dns=None, internal_fq_dns=None, external_ip_ranges=None,
internal_ip_ranges=None, internal_ports=None, fully_qualified_domain_names=None, ip_ranges=None,
ports=None, org_unit_id=None):
"""
:param str app_id:
:param dict cit_map:
"""
self.app_id = app_id
self.cit_alias_id_map = cit_map
self.company_id = company_id
self.name = name
self.description = description
self.owner_ids_and_types = owner_ids_and_types
self.fq_dns = fq_dns
self.internal_fq_dns = internal_fq_dns
self.external_ip_ranges = external_ip_ranges
self.internal_ip_ranges = internal_ip_ranges
self.internal_ports = internal_ports
self.fully_qualified_domain_names = fully_qualified_domain_names
self.ip_ranges = ip_ranges
self.ports = ports
self.org_unit_id = org_unit_id


class CommonConnection:

def auth(self):
Expand Down Expand Up @@ -702,6 +681,11 @@ def retrieve_ssh_config(self, ca_request):

@staticmethod
def process_server_response(r):
"""
:param requests.Response r:
:rtype: str or dict
"""
if r.status_code not in (HTTPStatus.OK, HTTPStatus.ACCEPTED, HTTPStatus.CREATED, HTTPStatus.CONFLICT):
try:
log_errors(r.json())
Expand All @@ -727,6 +711,9 @@ def process_server_response(r):
elif content_type.startswith(MIME_CSV):
log.debug(r.content.decode())
return r.status_code, r.content.decode()
elif content_type.startswith(MIME_OCTET_STREAM):
log.debug(r.content)
return r.status_code, r.content
else:
log.error("Unexpected content type: %s for request %s" % (content_type, r.request.url))
raise ServerUnexptedBehavior
Expand Down
Loading

0 comments on commit e602434

Please sign in to comment.