Skip to content

Commit 4ff72d1

Browse files
authored
Merge pull request #109 from Venafi/sans_vaas_updates
Sans support for VaaS
2 parents 87a3875 + 9eb21b9 commit 4ff72d1

7 files changed

Lines changed: 229 additions & 20 deletions

File tree

tests/test_pm.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from vcert.policy import (Policy, Subject, KeyPair, SubjectAltNames, Defaults, DefaultSubject, DefaultKeyPair,
2727
PolicySpecification)
2828
from vcert.policy.pm_cloud import (CA_TYPE_DIGICERT, CA_TYPE_ENTRUST, validate_policy_spec as validate_ps_vaas,
29-
get_ca_info, default_error_msg)
29+
get_ca_info, default_error_msg, ipv4, ipv6)
3030
from vcert.policy.pm_tpp import (is_service_generated_csr, validate_policy_spec as validate_ps_tpp, no_match_error_msg,
3131
too_many_error_msg, unsupported_error_msg, supported_key_types,
3232
supported_rsa_key_sizes, supported_elliptic_curves)
@@ -204,6 +204,20 @@ def test_ec_key_pair(self):
204204
self.assertIn('P521', ['P521', 'P384'], "[P521] is not in the allowed Elliptic Curves list")
205205
self.assertIn('P384', ['P521', 'P384'], "[P384] is not in the allowed Elliptic Curves list")
206206

207+
def test_create_policy_uri_ip_email(self):
208+
policy = get_policy_obj()
209+
policy.subject_alt_names.email_allowed = True
210+
policy.subject_alt_names.uri_allowed = True
211+
policy.subject_alt_names.ip_allowed = True
212+
uri_protocols = ["https", "ldaps", "spiffe"]
213+
policy.subject_alt_names.uri_protocols = uri_protocols
214+
ps = self._create_policy_cloud(policy=policy, defaults=get_defaults_obj())
215+
self.assertListEqual(ps.policy.subject_alt_names.ip_constraints, [ipv4, ipv6])
216+
self.assertListEqual(ps.policy.subject_alt_names.uri_protocols, uri_protocols)
217+
self.assertTrue(ps.policy.subject_alt_names.email_allowed)
218+
self.assertTrue(ps.policy.subject_alt_names.uri_allowed)
219+
self.assertTrue(ps.policy.subject_alt_names.ip_allowed)
220+
207221
def _create_policy_cloud(self, policy_spec=None, policy=None, defaults=None):
208222
zone = get_vaas_zone()
209223
response = create_policy(self.cloud_conn, zone, policy_spec, policy, defaults)

vcert/common.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,9 @@ def __init__(self, policy_id=None, company_id=None, name=None, system_generated=
234234
self.recommended_settings = recommended_settings
235235
self.csr_upload_allowed = csr_upload_allowed
236236
self.key_generated_by_venafi_allowed = key_generated_by_venafi_allowed
237+
self.email_regexes = None # type: list[str]
238+
self.ip_constraints_regexes = None # type: list[str]
239+
self.uri_regexes = None # type: list[str]
237240

238241
def __repr__(self):
239242
return "Policy:\n" + "\n".join([f" {k}: {v}" for k, v in (

vcert/connection_cloud.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
CSR_ATTR_COUNTRY = 'country'
5151
CSR_ATTR_SANS_BY_TYPE = 'subjectAlternativeNamesByType'
5252
CSR_ATTR_SANS_DNS = 'dnsNames'
53+
CSR_ATTR_SANS_IP_ADDR = 'ipAddresses'
54+
CSR_ATTR_SANS_EMAIL_ADDR = 'rfc822Names'
55+
CSR_ATTR_SANS_URIS = 'uniformResourceIdentifiers'
5356
CSR_ATTR_KEY_TYPE_PARAMS = 'keyTypeParameters'
5457
CSR_ATTR_KEY_TYPE = 'keyType'
5558
CSR_ATTR_KEY_LENGTH = 'keyLength'
@@ -255,6 +258,11 @@ def _parse_policy_response_to_object(d):
255258
d['csrUploadAllowed'] if 'csrUploadAllowed' in d else None,
256259
d['keyGeneratedByVenafiAllowed'] if 'keyGeneratedByVenafiAllowed' in d else None
257260
)
261+
policy.email_regexes = d['sanRfc822NameRegexes'] if 'sanRfc822NameRegexes' in d else None
262+
policy.ip_constraints_regexes = d['sanIpAddressRegexes'] if 'sanIpAddressRegexes' in d else None
263+
policy.uri_regexes = d['sanUniformResourceIdentifierRegexes'] if 'sanUniformResourceIdentifierRegexes' in d \
264+
else None
265+
258266
for kt in d.get('keyTypes', []):
259267
key_type = kt['keyType'].lower()
260268
if key_type == KeyType.RSA:
@@ -814,8 +822,10 @@ def _get_service_generated_csr_attr(self, request, zone):
814822

815823
if len(request.san_dns) > 0:
816824
sans = {
817-
CSR_ATTR_SANS_DNS: request.san_dns
818-
# TODO: Other sans should be added here
825+
CSR_ATTR_SANS_DNS: request.san_dns,
826+
CSR_ATTR_SANS_IP_ADDR: request.ip_addresses,
827+
CSR_ATTR_SANS_EMAIL_ADDR: request.email_addresses,
828+
CSR_ATTR_SANS_URIS: request.uniform_resource_identifiers
819829
}
820830
csr_attr_map[CSR_ATTR_SANS_BY_TYPE] = sans
821831

vcert/parser/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
FIELD_EMAIL_ALLOWED = 'emailAllowed'
4747
FIELD_URI_ALLOWED = 'uriAllowed'
4848
FIELD_UPN_ALLOWED = 'upnAllowed'
49+
FIELD_URI_PROTOCOLS = 'uriProtocols'
50+
FIELD_IP_CONSTRAINTS = 'ipConstraints'
4951

5052
FIELD_DEFAULTS = 'defaults'
5153
FIELD_DEFAULT_DOMAIN = 'domain'

vcert/parser/utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ def parse_data(data):
7979
subject_alt_names.ip_allowed = sans[FIELD_IP_ALLOWED] if FIELD_IP_ALLOWED in sans else None
8080
subject_alt_names.upn_allowed = sans[FIELD_UPN_ALLOWED] if FIELD_UPN_ALLOWED in sans else None
8181
subject_alt_names.uri_allowed = sans[FIELD_URI_ALLOWED] if FIELD_URI_ALLOWED in sans else None
82+
subject_alt_names.uri_protocols = sans[FIELD_URI_PROTOCOLS] if FIELD_URI_PROTOCOLS in sans else []
83+
subject_alt_names.ip_constraints = sans[FIELD_IP_CONSTRAINTS] if FIELD_IP_CONSTRAINTS in sans else []
8284

8385
policy.subject = subject
8486
policy.key_pair = key_pair
@@ -176,7 +178,9 @@ def parse_policy_spec(policy_spec):
176178
FIELD_EMAIL_ALLOWED: sans.email_allowed,
177179
FIELD_IP_ALLOWED: sans.ip_allowed,
178180
FIELD_UPN_ALLOWED: sans.upn_allowed,
179-
FIELD_URI_ALLOWED: sans.uri_allowed
181+
FIELD_URI_ALLOWED: sans.uri_allowed,
182+
FIELD_IP_CONSTRAINTS: sans.ip_constraints,
183+
FIELD_URI_PROTOCOLS: sans.uri_protocols
180184
}
181185

182186
ds_data = dict()

vcert/policy/pm_cloud.py

Lines changed: 186 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,28 @@
2727
REQUESTER_NAME = 'Venafi Cloud Service'
2828
REQUESTER_EMAIL = 'no-reply@venafi.cloud'
2929
REQUESTER_PHONE = '801-555-0123'
30-
ALLOW_ALL = '.*'
30+
ipv4 = "v4"
31+
ipv6 = "v6"
32+
ipv4_private = "v4private"
33+
ipv6_private = "v6private"
34+
re_allow_all = '.*'
35+
re_allow_all_email = '.*@.*'
36+
re_ipv4 = "\\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}\\b"
37+
re_ipv6 = "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]" \
38+
"{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" \
39+
"([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" \
40+
"[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%" \
41+
"[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|" \
42+
"(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.)" \
43+
"{3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"
44+
re_ipv4_private = "^(172\\.(1[6-9]\\.|2[0-9]\\.|3[0-1]\\.)|192\\.168\\.|10\\.).*"
45+
re_ipv6_private = "^(::1$)|([fF][cCdD]).*"
46+
supported_ip_protocols = {
47+
ipv4: re_ipv4,
48+
ipv6: re_ipv6,
49+
ipv4_private: re_ipv4_private,
50+
ipv6_private: re_ipv6_private
51+
}
3152
DEFAULT_MAX_VALID_DAYS = 365
3253
DEFAULT_HASH_ALGORITHM = 'SHA256'
3354

@@ -119,9 +140,26 @@ def build_policy_spec(cit, ca_info, subject_cn_to_str=True):
119140
p.key_pair = kp if create_kp else None
120141

121142
sans = SubjectAltNames(False, False, False, False, False)
143+
create_sans = False
122144
if cit.SANRegexes:
123145
sans.dns_allowed = True
124-
p.subject_alt_names = sans
146+
create_sans = True
147+
148+
if cit.email_regexes and len(cit.email_regexes) > 0:
149+
sans.email_allowed = True
150+
create_sans = True
151+
152+
if cit.ip_constraints_regexes and len(cit.ip_constraints_regexes) > 0:
153+
sans.ip_allowed = True
154+
create_sans = True
155+
sans.ip_constraints = resolve_ip_constraints(cit.ip_constraints_regexes)
156+
157+
if cit.uri_regexes and len(cit.uri_regexes) > 0:
158+
sans.uri_allowed = True
159+
create_sans = True
160+
sans.uri_protocols = resolve_uri_protocols(cit.uri_regexes)
161+
162+
p.subject_alt_names = sans if create_sans else None
125163

126164
ps.policy = p
127165

@@ -201,8 +239,17 @@ def validate_policy_spec(policy_spec):
201239
sans = get_sans(policy_spec.policy.subject_alt_names)
202240
if len(sans) > 0:
203241
for k, v in sans.items():
204-
if v is True and not (k == RPA.TPP_DNS_ALLOWED):
242+
if v is True and (k == RPA.TPP_UPN_ALLOWED):
205243
raise VenafiError(f"Subject Alt name [{k}] is not allowed by VaaS")
244+
if v is True and (k == RPA.TPP_URI_ALLOWED):
245+
if len(p.subject_alt_names.uri_protocols) == 0:
246+
raise VenafiError(f"'uriAllowed' attribute is True but 'uriProtocols' list is empty")
247+
if v is True and (k == RPA.TPP_IP_ALLOWED):
248+
ip_constraints = p.subject_alt_names.ip_constraints
249+
if ip_constraints and len(ip_constraints) > 0:
250+
invalid_value = get_invalid_ip_constraint(ip_constraints)
251+
if invalid_value:
252+
raise VenafiError(f"The IP constraint [{invalid_value}] is not supported by VaaS")
206253

207254
# validate default subject values against policy values
208255
if policy_spec.defaults and policy_spec.defaults.subject and policy_spec.policy.subject:
@@ -325,13 +372,26 @@ def get_sans(names):
325372
return sans
326373

327374

375+
def get_invalid_ip_constraint(ip_list):
376+
"""
377+
378+
:param list[str] ip_list:
379+
:rtype: str
380+
"""
381+
for ip_value in ip_list:
382+
if ip_value not in supported_ip_protocols.keys():
383+
return ip_value
384+
385+
return None
386+
387+
328388
def is_valid_policy_value(policy_values, default_value):
329389
"""
330390
:param list[str] policy_values:
331391
:param str default_value:
332392
:rtype: bool
333393
"""
334-
if len(policy_values) == 1 and policy_values[0] == ALLOW_ALL:
394+
if len(policy_values) == 1 and policy_values[0] == re_allow_all:
335395
return True
336396
return True if default_value in policy_values else False
337397

@@ -344,7 +404,7 @@ def member_of(sub_list, collection):
344404
:param list[str] collection:
345405
:rtype: bool
346406
"""
347-
if len(sub_list) == 1 and sub_list[0] == ALLOW_ALL:
407+
if len(sub_list) == 1 and sub_list[0] == re_allow_all:
348408
return True
349409
return all(x in collection for x in sub_list)
350410

@@ -408,39 +468,66 @@ def build_cit_request(ps, ca_details):
408468
if ps.policy and len(ps.policy.domains) > 0:
409469
regex_value = convert_to_regex(ps.policy.domains, ps.policy.wildcard_allowed)
410470
request['subjectCNRegexes'] = regex_value
411-
if ps.policy.subject_alt_names and ps.policy.subject_alt_names.dns_allowed is not None:
412-
if ps.policy.subject_alt_names.dns_allowed:
471+
sans = ps.policy.subject_alt_names
472+
if sans and sans.dns_allowed is not None:
473+
if sans.dns_allowed:
413474
request['sanRegexes'] = regex_value
414475
else:
415476
request['sanRegexes'] = regex_value
477+
478+
if sans and sans.email_allowed:
479+
email_regex_list = convert_to_email_regex(ps.policy.domains)
480+
request['sanRfc822NameRegexes'] = email_regex_list
481+
482+
if sans and sans.uri_allowed:
483+
uri_regex_list = convert_to_uri_regex(sans.uri_protocols, ps.policy.domains)
484+
request['sanUniformResourceIdentifierRegexes'] = uri_regex_list
485+
486+
# sanIpAddressRegexes
487+
416488
else:
417-
request['subjectCNRegexes'] = [ALLOW_ALL]
418-
request['sanRegexes'] = [ALLOW_ALL]
489+
request['subjectCNRegexes'] = [re_allow_all]
490+
request['sanRegexes'] = [re_allow_all]
491+
if ps.policy:
492+
sans = ps.policy.subject_alt_names
493+
if sans and sans.email_allowed:
494+
request['sanRfc822NameRegexes'] = [re_allow_all_email]
495+
if sans and sans.uri_allowed:
496+
uri_regex_list = convert_to_uri_regex(sans.uri_protocols, [re_allow_all])
497+
request['sanUniformResourceIdentifierRegexes'] = uri_regex_list
498+
if sans and sans.ip_allowed:
499+
request['sanIpAddressRegexes'] = []
500+
501+
if ps.policy and ps.policy.subject_alt_names and ps.policy.subject_alt_names.ip_allowed:
502+
if ps.policy.subject_alt_names.ip_constraints and len(ps.policy.subject_alt_names.ip_constraints) > 0:
503+
request['sanIpAddressRegexes'] = resolve_ip_regexes(ps.policy.subject_alt_names.ip_constraints)
504+
else:
505+
request['sanIpAddressRegexes'] = [re_ipv4, re_ipv6]
419506

420507
if ps.policy and ps.policy.subject and len(ps.policy.subject.orgs) > 0:
421508
request['subjectORegexes'] = ps.policy.subject.orgs
422509
else:
423-
request['subjectORegexes'] = [ALLOW_ALL]
510+
request['subjectORegexes'] = [re_allow_all]
424511

425512
if ps.policy and ps.policy.subject and len(ps.policy.subject.org_units) > 0:
426513
request['subjectOURegexes'] = ps.policy.subject.org_units
427514
else:
428-
request['subjectOURegexes'] = [ALLOW_ALL]
515+
request['subjectOURegexes'] = [re_allow_all]
429516

430517
if ps.policy and ps.policy.subject and len(ps.policy.subject.localities) > 0:
431518
request['subjectLRegexes'] = ps.policy.subject.localities
432519
else:
433-
request['subjectLRegexes'] = [ALLOW_ALL]
520+
request['subjectLRegexes'] = [re_allow_all]
434521

435522
if ps.policy and ps.policy.subject and len(ps.policy.subject.states) > 0:
436523
request['subjectSTRegexes'] = ps.policy.subject.states
437524
else:
438-
request['subjectSTRegexes'] = [ALLOW_ALL]
525+
request['subjectSTRegexes'] = [re_allow_all]
439526

440527
if ps.policy and ps.policy.subject and len(ps.policy.subject.countries) > 0:
441528
request['subjectCValues'] = ps.policy.subject.countries
442529
else:
443-
request['subjectCValues'] = [ALLOW_ALL]
530+
request['subjectCValues'] = [re_allow_all]
444531

445532
key_types = []
446533
if ps.policy and ps.policy.key_pair and len(ps.policy.key_pair.key_types) > 0:
@@ -529,13 +616,15 @@ def build_cit_request(ps, ca_details):
529616

530617
domain_regex = '[a-z]{1}[a-z0-9.-]*\\.'
531618
domain_regex_wildcard = '[*a-z]{1}[a-z0-9.-]*\\.'
619+
email_prefix_regex = '.*@{}'
620+
uri_protocols_regex = "({})://.*\\."
532621

533622

534623
def convert_to_regex(domains, wildcard_allowed):
535624
"""
536625
:param list[str] domains:
537626
:param bool wildcard_allowed:
538-
:rtype: dict
627+
:rtype: list[str]
539628
"""
540629
regex_list = []
541630
for d in domains:
@@ -548,6 +637,88 @@ def convert_to_regex(domains, wildcard_allowed):
548637
return regex_list
549638

550639

640+
def convert_to_email_regex(emails_list):
641+
"""
642+
643+
:param list[str] emails_list:
644+
:rtype: list[str]
645+
"""
646+
regex_list = []
647+
for email in emails_list:
648+
current = email.replace('.', '\\.')
649+
current = email_prefix_regex.format(current)
650+
regex_list.append(current)
651+
652+
return regex_list
653+
654+
655+
def convert_to_uri_regex(uri_protocols, domains_list):
656+
"""
657+
658+
:param list[str] uri_protocols:
659+
:param list[str] domains_list:
660+
:rtype: list[str]
661+
"""
662+
protocol_expr = "|".join(uri_protocols)
663+
protocol_expr = uri_protocols_regex.format(protocol_expr)
664+
665+
regex_list = []
666+
for d in domains_list:
667+
current = d.replace('.', '\\.')
668+
current = f"{protocol_expr}{current}"
669+
regex_list.append(current)
670+
671+
return regex_list
672+
673+
674+
def resolve_ip_regexes(ip_protocols):
675+
"""
676+
677+
:param list[str] ip_protocols:
678+
:rtype: list[str]
679+
"""
680+
ip_regexes = list()
681+
for ip_str in ip_protocols:
682+
regex = supported_ip_protocols.get(ip_str)
683+
if regex:
684+
ip_regexes.append(regex)
685+
686+
return ip_regexes
687+
688+
689+
def resolve_ip_constraints(ip_constraints_list):
690+
"""
691+
692+
:param list[str] ip_constraints_list:
693+
:rtype: list[str]
694+
"""
695+
ip_list = list()
696+
for ip_regex in ip_constraints_list:
697+
for k, v in supported_ip_protocols.items():
698+
if ip_regex == v:
699+
ip_list.append(k)
700+
break
701+
return ip_list
702+
703+
704+
def resolve_uri_protocols(uri_regexes_list):
705+
"""
706+
707+
:param list[str] uri_regexes_list:
708+
:rtype: list[str]
709+
"""
710+
protocols_list = list()
711+
for uri_regex in uri_regexes_list:
712+
index = uri_regex.index(')://')
713+
sub_str = uri_regex[1:index]
714+
current_protocols = sub_str.split("|")
715+
for p in current_protocols:
716+
if p not in protocols_list:
717+
protocols_list.append(p)
718+
719+
return protocols_list
720+
721+
551722
def convert_to_string(regexes, wildcard_allowed):
552723
"""
553724
:param list[str] regexes:

0 commit comments

Comments
 (0)