Imported Debian patch 4.0.5-6~numeezy

This commit is contained in:
Alexandre Ellert
2016-02-17 15:07:45 +01:00
committed by Mario Fetka
parent c44de33144
commit 10dfc9587b
1203 changed files with 53869 additions and 241462 deletions

Binary file not shown.

View File

@@ -1,12 +1,10 @@
# Authors:
# Ade Lee <alee@redhat.com>
# Andrew Wnuk <awnuk@redhat.com>
# Jason Gerard DeRose <jderose@redhat.com>
# Rob Crittenden <rcritten@@redhat.com>
# John Dennis <jdennis@redhat.com>
# Fraser Tweedale <ftweedal@redhat.com>
#
# Copyright (C) 2014, 2015 Red Hat
# Copyright (C) 2009 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or modify
@@ -36,7 +34,7 @@ variety of names, the open source version is called "dogtag".
CMS consists of a number of servlets which in rough terms can be thought of as
RPC commands. A servlet is invoked by making an HTTP request to a specific URL
and passing URL arguments. Normally CMS responds with an HTTP response consisting
and passing URL arguments. Normally CMS responds with an HTTP reponse consisting
of HTML to be rendered by a web browser. This HTTP HTML response has both
Javascript SCRIPT components and HTML rendering code. One of the Javascript
SCRIPT blocks holds the data for the result. The rest of the response is derived
@@ -44,13 +42,13 @@ from templates associated with the servlet which may be customized. The
templates pull the result data from Javascript variables.
One way to get the result data is to parse the HTML looking for the Javascript
variable initializations. Simple string searches are not a robust method. First of
varible initializations. Simple string searchs are not a robust method. First of
all one must be sure the string is only found in a Javascript SCRIPT block and
not somewhere else in the HTML document. Some of the Javascript variable
initializations are rather complex (e.g. lists of structures). It would be hard
to correctly parse such complex and diverse Javascript. Existing Javascript
parsers are not generally available. Finally, it's important to know the
character encoding for strings. There is a somewhat complex set of precedent
character encoding for strings. There is a somewhat complex set of precident
rules for determining the current character encoding from the HTTP header,
meta-equiv tags, mime Content-Type and charset attributes on HTML elements. All
of this means trying to read the result data from a CMS HTML response is
@@ -121,7 +119,7 @@ values. Python also nicely handles type promotion transparently between int
and long objects. For example if you multiply two int objects you may get back
a long object if necessary. In general Python int and long objects may be
freely mixed without the programmer needing to be aware of which type of
integral object is being operated on.
intergral object is being operated on.
The leads to the following rule, always parse a string representing an
integral value using the int() constructor even if it might have large
@@ -164,16 +162,16 @@ Basic rules on handling these values
1. Reading a serial number from CMS requires conversion from hexadecimal
by converting it into a Python int or long object, use the int constructor:
serial_number = int(serial_number, 16)
>>> serial_number = int(serial_number, 16)
2. Big integers passed to XMLRPC must be decimal unicode strings
unicode(serial_number)
>>> unicode(serial_number)
3. Big integers received from XMLRPC must be converted back to int or long
objects from the decimal string representation.
serial_number = int(serial_number)
>>> serial_number = int(serial_number)
Xpath pattern matching on node names:
-------------------------------------
@@ -203,7 +201,7 @@ want to pass the node name. To do this use the name() function. One way we could
solve the chapter problem above is by using a predicate which says if the node
name begins with 'chapter' it's a match. Here is how you can do that.
doc.xpath("//book/*[starts-with(name(), 'chapter')]/section[2]")
>>> doc.xpath("//book/*[starts-with(name(), 'chapter')]/section[2]")
The built-in starts-with() returns true if its first argument starts with its
second argument. Thus the example above says if the node name of the second
@@ -220,10 +218,10 @@ it to bind to those namespaces during its evaluation. Then we just use the
EXSLT regular expression match() function on the node name. Here is how this is
done:
regexpNS = "http://exslt.org/regular-expressions"
find = etree.XPath("//book/*[re:match(name(), '^chapter(_\d+)$')]/section[2]",
namespaces={'re':regexpNS}
find(doc)
>>> regexpNS = "http://exslt.org/regular-expressions"
>>> find = etree.XPath("//book/*[re:match(name(), '^chapter(_\d+)$')]/section[2]",
... namespaces={'re':regexpNS}
>>> find(doc)
What is happening here is that etree.XPath() has returned us an evaluator
function which we bind to the name 'find'. We've passed it a set of namespaces
@@ -231,36 +229,20 @@ as a dict via the 'namespaces' keyword parameter of etree.XPath(). The predicate
for the second location step uses the 're:' namespace to find the function name
'match'. The re:match() takes a string to search as its first argument and a
regular expression pattern as its second argument. In this example the string
to search is the node name of the location step because we called the built-in
to seach is the node name of the location step because we called the built-in
node() function of XPath. The regular expression pattern we've passed says it's
a match if the string begins with 'chapter' is followed by any number of
digits and nothing else follows.
'''
import datetime
import json
from lxml import etree
import os
import tempfile
import urllib2
import datetime
import time
import pki
from pki.client import PKIConnection
import pki.crypto as cryptoutil
from pki.kra import KRAClient
import six
from six.moves import urllib
from ipalib import Backend
from ipapython.dn import DN
import ipapython.cookie
import ipapython.dogtag
from ipapython import ipautil
from ipaserver.install.certs import CertDB
if six.PY3:
unicode = str
# These are general status return values used when
# CMSServlet.outputError() is invoked.
@@ -278,7 +260,6 @@ CMS_STATUS_REJECTED = 5
CMS_STATUS_ERROR = 6
CMS_STATUS_EXCEPTION = 7
def cms_request_status_to_string(request_status):
'''
:param request_status: The integral request status value
@@ -309,7 +290,7 @@ def parse_and_set_boolean_xml(node, response, response_name):
'''
:param node: xml node object containing value to parse for boolean result
:param response: response dict to set boolean result in
:param response_name: name of the response value to set
:param response_name: name of the respone value to set
:except ValueError:
Read the value out of a xml text node and interpret it as a boolean value.
@@ -665,7 +646,7 @@ def parse_check_request_result_xml(doc):
+-------------------------+---------------+-------------------+-----------------+
|requestId |string |request_id |string |
+-------------------------+---------------+-------------------+-----------------+
|status |string |cert_request_status|unicode [1]_ |
|staus |string |cert_request_status|unicode [1]_ |
+-------------------------+---------------+-------------------+-----------------+
|createdOn |long, timestamp|created_on |datetime.datetime|
+-------------------------+---------------+-------------------+-----------------+
@@ -1218,79 +1199,26 @@ def parse_unrevoke_cert_xml(doc):
return response
def host_has_service(host, ldap2, service='CA'):
"""
:param host: A host which might be a master for a service.
:param ldap2: connection to the local database
:param service: The service for which the host might be a master.
:return: (true, false)
Check if a specified host is a master for a specified service.
"""
base_dn = DN(('cn', host), ('cn', 'masters'), ('cn', 'ipa'),
('cn', 'etc'), api.env.basedn)
filter_attrs = {
'objectClass': 'ipaConfigObject',
'cn': service,
'ipaConfigString': 'enabledService',
}
query_filter = ldap2.make_filter(filter_attrs, rules='&')
try:
ent, trunc = ldap2.find_entries(filter=query_filter, base_dn=base_dn)
if len(ent):
return True
except Exception:
pass
return False
def select_any_master(ldap2, service='CA'):
"""
:param ldap2: connection to the local database
:param service: The service for which we're looking for a master.
:return: host as str
Select any host which is a master for a specified service.
"""
base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
api.env.basedn)
filter_attrs = {
'objectClass': 'ipaConfigObject',
'cn': service,
'ipaConfigString': 'enabledService',}
query_filter = ldap2.make_filter(filter_attrs, rules='&')
try:
ent, trunc = ldap2.find_entries(filter=query_filter, base_dn=base_dn)
if len(ent):
entry = random.choice(ent)
return entry.dn[1].value
except Exception:
pass
return None
#-------------------------------------------------------------------------------
from ipalib import api, errors, SkipPluginModule
from ipalib import api, SkipPluginModule
if api.env.ra_plugin != 'dogtag':
# In this case, abort loading this plugin module...
raise SkipPluginModule(reason='dogtag not selected as RA plugin')
import os, random
from ipaserver.plugins import rabase
from ipalib.errors import CertificateOperationError
from ipalib.constants import TYPE_ERROR
from ipalib.util import cachedproperty
from ipapython import dogtag
from ipalib import _
from ipaplatform.paths import paths
class ra(rabase.rabase):
"""
Request Authority backend plugin.
"""
DEFAULT_PROFILE = dogtag.DEFAULT_PROFILE
def __init__(self, api):
def __init__(self):
if api.env.in_tree:
self.sec_dir = api.env.dot_ipa + os.sep + 'alias'
self.pwd_file = self.sec_dir + os.sep + '.pwd'
@@ -1307,7 +1235,7 @@ class ra(rabase.rabase):
f.close()
except IOError:
self.password = ''
super(ra, self).__init__(api)
super(ra, self).__init__()
def raise_certificate_operation_error(self, func_name, err_msg=None, detail=None):
"""
@@ -1328,7 +1256,58 @@ class ra(rabase.rabase):
err_msg = u'%s (%s)' % (err_msg, detail)
self.error('%s.%s(): %s', self.fullname, func_name, err_msg)
raise errors.CertificateOperationError(error=err_msg)
raise CertificateOperationError(error=err_msg)
def _host_has_service(self, host, service='CA'):
"""
:param host: A host which might be a master for a service.
:param service: The service for which the host might be a master.
:return: (true, false)
Check if a specified host is a master for a specified service.
"""
ldap2 = self.api.Backend.ldap2
base_dn = DN(('cn', host), ('cn', 'masters'), ('cn', 'ipa'),
('cn', 'etc'), api.env.basedn)
filter_attrs = {
'objectClass': 'ipaConfigObject',
'cn': service,
'ipaConfigString': 'enabledService',
}
filter = ldap2.make_filter(filter_attrs, rules='&')
try:
ent, trunc = ldap2.find_entries(filter=filter, base_dn=base_dn)
if len(ent):
return True
except Exception, e:
pass
return False
def _select_any_master(self, service='CA'):
"""
:param service: The service for which we're looking for a master.
:return: host
as str
Select any host which is a master for a specified service.
"""
ldap2 = self.api.Backend.ldap2
base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
api.env.basedn)
filter_attrs = {
'objectClass': 'ipaConfigObject',
'cn': service,
'ipaConfigString': 'enabledService',
}
filter = ldap2.make_filter(filter_attrs, rules='&')
try:
ent, trunc = ldap2.find_entries(filter=filter, base_dn=base_dn)
if len(ent):
entry = random.choice(ent)
return entry.dn[1].value
except Exception, e:
pass
return None
@cachedproperty
def ca_host(self):
@@ -1338,13 +1317,12 @@ class ra(rabase.rabase):
Select our CA host.
"""
ldap2 = self.api.Backend.ldap2
if host_has_service(api.env.ca_host, ldap2, "CA"):
if self._host_has_service(host=api.env.ca_host):
return api.env.ca_host
if api.env.host != api.env.ca_host:
if host_has_service(api.env.host, ldap2, "CA"):
if self._host_has_service(host=api.env.host):
return api.env.host
host = select_any_master(ldap2)
host = self._select_any_master()
if host:
return host
else:
@@ -1354,8 +1332,8 @@ class ra(rabase.rabase):
"""
:param url: The URL to post to.
:param kw: Keyword arguments to encode into POST body.
:return: (http_status, http_headers, http_body)
as (integer, dict, str)
:return: (http_status, http_reason_phrase, http_headers, http_body)
as (integer, unicode, dict, str)
Perform an HTTP request.
"""
@@ -1365,8 +1343,8 @@ class ra(rabase.rabase):
"""
:param url: The URL to post to.
:param kw: Keyword arguments to encode into POST body.
:return: (http_status, http_headers, http_body)
as (integer, dict, str)
:return: (http_status, http_reason_phrase, http_headers, http_body)
as (integer, unicode, dict, str)
Perform an HTTPS request
"""
@@ -1385,8 +1363,7 @@ class ra(rabase.rabase):
parser = etree.XMLParser()
doc = etree.fromstring(xml_text, parser)
result = parse_func(doc)
self.debug("%s() xml_text:\n%s\n"
"parse_result:\n%s" % (parse_func.__name__, xml_text, result))
self.debug("%s() xml_text:\n%s\nparse_result:\n%s" % (parse_func.__name__, xml_text, result))
return result
def check_request_status(self, request_id):
@@ -1426,16 +1403,16 @@ class ra(rabase.rabase):
self.debug('%s.check_request_status()', self.fullname)
# Call CMS
http_status, http_headers, http_body = \
http_status, http_reason_phrase, http_headers, http_body = \
self._request('/ca/ee/ca/checkRequest',
self.env.ca_port,
requestId=request_id,
xml='true')
# Parse and handle errors
if http_status != 200:
if (http_status != 200):
self.raise_certificate_operation_error('check_request_status',
detail=http_status)
detail=http_reason_phrase)
parse_result = self.get_parse_result_xml(http_body, parse_check_request_result_xml)
request_status = parse_result['request_status']
@@ -1446,14 +1423,14 @@ class ra(rabase.rabase):
# Return command result
cmd_result = {}
if 'serial_numbers' in parse_result and len(parse_result['serial_numbers']) > 0:
if parse_result.has_key('serial_numbers') and len(parse_result['serial_numbers']) > 0:
# see module documentation concerning serial numbers and XMLRPC
cmd_result['serial_number'] = unicode(parse_result['serial_numbers'][0])
if 'request_id' in parse_result:
if parse_result.has_key('request_id'):
cmd_result['request_id'] = parse_result['request_id']
if 'cert_request_status' in parse_result:
if parse_result.has_key('cert_request_status'):
cmd_result['cert_request_status'] = parse_result['cert_request_status']
return cmd_result
@@ -1463,10 +1440,10 @@ class ra(rabase.rabase):
Retrieve an existing certificate.
:param serial_number: Certificate serial number. Must be a string value
because serial numbers may be of any magnitude and
because serial numbers may be of any magnitue and
XMLRPC cannot handle integers larger than 64-bit.
The string value should be decimal, but may optionally
be prefixed with a hex radix prefix if the integral value
be prefixed with a hex radix prefix if the integal value
is represented as hexadecimal. If no radix prefix is
supplied the string will be interpreted as decimal.
@@ -1511,7 +1488,7 @@ class ra(rabase.rabase):
serial_number = int(serial_number, 0)
# Call CMS
http_status, http_headers, http_body = \
http_status, http_reason_phrase, http_headers, http_body = \
self._sslget('/ca/agent/ca/displayBySerial',
self.env.ca_agent_port,
serialNumber=str(serial_number),
@@ -1519,9 +1496,9 @@ class ra(rabase.rabase):
# Parse and handle errors
if http_status != 200:
if (http_status != 200):
self.raise_certificate_operation_error('get_certificate',
detail=http_status)
detail=http_reason_phrase)
parse_result = self.get_parse_result_xml(http_body, parse_display_cert_xml)
request_status = parse_result['request_status']
@@ -1533,24 +1510,23 @@ class ra(rabase.rabase):
# Return command result
cmd_result = {}
if 'certificate' in parse_result:
if parse_result.has_key('certificate'):
cmd_result['certificate'] = parse_result['certificate']
if 'serial_number' in parse_result:
if parse_result.has_key('serial_number'):
# see module documentation concerning serial numbers and XMLRPC
cmd_result['serial_number'] = unicode(parse_result['serial_number'])
cmd_result['serial_number_hex'] = u'0x%X' % int(cmd_result['serial_number'])
if 'revocation_reason' in parse_result:
if parse_result.has_key('revocation_reason'):
cmd_result['revocation_reason'] = parse_result['revocation_reason']
return cmd_result
def request_certificate(self, csr, profile_id, request_type='pkcs10'):
def request_certificate(self, csr, request_type='pkcs10'):
"""
:param csr: The certificate signing request.
:param profile_id: The profile to use for the request.
:param request_type: The request type (defaults to ``'pkcs10'``).
Submit certificate signing request.
@@ -1579,17 +1555,17 @@ class ra(rabase.rabase):
self.debug('%s.request_certificate()', self.fullname)
# Call CMS
http_status, http_headers, http_body = \
http_status, http_reason_phrase, http_headers, http_body = \
self._sslget('/ca/eeca/ca/profileSubmitSSLClient',
self.env.ca_ee_port,
profileId=profile_id,
profileId='caIPAserviceCert',
cert_request_type=request_type,
cert_request=csr,
xml='true')
# Parse and handle errors
if http_status != 200:
if (http_status != 200):
self.raise_certificate_operation_error('request_certificate',
detail=http_status)
detail=http_reason_phrase)
parse_result = self.get_parse_result_xml(http_body, parse_profile_submit_result_xml)
# Note different status return, it's not request_status, it's error_code
@@ -1608,18 +1584,18 @@ class ra(rabase.rabase):
return cmd_result
request = parse_result['requests'][0]
if 'serial_number' in request:
if request.has_key('serial_number'):
# see module documentation concerning serial numbers and XMLRPC
cmd_result['serial_number'] = unicode(request['serial_number'])
cmd_result['serial_number_hex'] = u'0x%X' % request['serial_number']
if 'certificate' in request:
if request.has_key('certificate'):
cmd_result['certificate'] = request['certificate']
if 'request_id' in request:
if request.has_key('request_id'):
cmd_result['request_id'] = request['request_id']
if 'subject' in request:
if request.has_key('subject'):
cmd_result['subject'] = request['subject']
return cmd_result
@@ -1628,10 +1604,10 @@ class ra(rabase.rabase):
def revoke_certificate(self, serial_number, revocation_reason=0):
"""
:param serial_number: Certificate serial number. Must be a string value
because serial numbers may be of any magnitude and
because serial numbers may be of any magnitue and
XMLRPC cannot handle integers larger than 64-bit.
The string value should be decimal, but may optionally
be prefixed with a hex radix prefix if the integral value
be prefixed with a hex radix prefix if the integal value
is represented as hexadecimal. If no radix prefix is
supplied the string will be interpreted as decimal.
:param revocation_reason: Integer code of revocation reason.
@@ -1658,7 +1634,7 @@ class ra(rabase.rabase):
serial_number = int(serial_number, 0)
# Call CMS
http_status, http_headers, http_body = \
http_status, http_reason_phrase, http_headers, http_body = \
self._sslget('/ca/agent/ca/doRevoke',
self.env.ca_agent_port,
op='revoke',
@@ -1668,9 +1644,9 @@ class ra(rabase.rabase):
xml='true')
# Parse and handle errors
if http_status != 200:
if (http_status != 200):
self.raise_certificate_operation_error('revoke_certificate',
detail=http_status)
detail=http_reason_phrase)
parse_result = self.get_parse_result_xml(http_body, parse_revoke_cert_xml)
request_status = parse_result['request_status']
@@ -1692,10 +1668,10 @@ class ra(rabase.rabase):
def take_certificate_off_hold(self, serial_number):
"""
:param serial_number: Certificate serial number. Must be a string value
because serial numbers may be of any magnitude and
because serial numbers may be of any magnitue and
XMLRPC cannot handle integers larger than 64-bit.
The string value should be decimal, but may optionally
be prefixed with a hex radix prefix if the integral value
be prefixed with a hex radix prefix if the integal value
is represented as hexadecimal. If no radix prefix is
supplied the string will be interpreted as decimal.
@@ -1721,16 +1697,16 @@ class ra(rabase.rabase):
serial_number = int(serial_number, 0)
# Call CMS
http_status, http_headers, http_body = \
http_status, http_reason_phrase, http_headers, http_body = \
self._sslget('/ca/agent/ca/doUnrevoke',
self.env.ca_agent_port,
serialNumber=str(serial_number),
xml='true')
# Parse and handle errors
if http_status != 200:
if (http_status != 200):
self.raise_certificate_operation_error('take_certificate_off_hold',
detail=http_status)
detail=http_reason_phrase)
parse_result = self.get_parse_result_xml(http_body, parse_unrevoke_cert_xml)
@@ -1743,7 +1719,7 @@ class ra(rabase.rabase):
# Return command result
cmd_result = {}
if 'error_string' in parse_result:
if parse_result.has_key('error_string'):
cmd_result['error_string'] = parse_result['error_string']
if parse_result.get('unrevoked') == 'yes':
@@ -1841,25 +1817,23 @@ class ra(rabase.rabase):
payload = etree.tostring(doc, pretty_print=False, xml_declaration=True, encoding='UTF-8')
self.debug('%s.find(): request: %s', self.fullname, payload)
url = 'http://%s/ca/rest/certs/search?size=%d' % (
ipautil.format_netloc(self.ca_host, 8080),
options.get('sizelimit', 100))
url = 'http://%s/ca/rest/certs/search?size=%d' % (ipautil.format_netloc(self.ca_host, ipapython.dogtag.configured_constants().UNSECURE_PORT), options.get('sizelimit', 100))
opener = urllib.request.build_opener()
opener = urllib2.build_opener()
opener.addheaders = [('Accept-Encoding', 'gzip, deflate'),
('User-Agent', 'IPA')]
req = urllib.request.Request(url=url, data=payload, headers={'Content-Type': 'application/xml'})
req = urllib2.Request(url=url, data=payload, headers={'Content-Type': 'application/xml'})
try:
response = opener.open(req)
except urllib.error.HTTPError as e:
except urllib2.HTTPError, e:
self.debug('HTTP Response code: %d' % e.getcode())
if e.getcode() == 501:
self.raise_certificate_operation_error('find',
detail=_('find not supported on CAs upgraded from 9 to 10'))
self.raise_certificate_operation_error('find',
detail=e.msg)
except urllib.error.URLError as e:
except urllib2.URLError, e:
self.raise_certificate_operation_error('find',
detail=e.reason)
@@ -1868,7 +1842,7 @@ class ra(rabase.rabase):
parser = etree.XMLParser()
try:
doc = etree.fromstring(data[0], parser)
except etree.XMLSyntaxError as e:
except etree.XMLSyntaxError, e:
self.raise_certificate_operation_error('find',
detail=e.msg)
@@ -1892,257 +1866,4 @@ class ra(rabase.rabase):
return results
api.register(ra)
# ----------------------------------------------------------------------------
class kra(Backend):
"""
KRA backend plugin (for Vault)
"""
def __init__(self, api, kra_port=443):
self.kra_port = kra_port
super(kra, self).__init__(api)
@property
def kra_host(self):
"""
:return: host
as str
Select our KRA host.
"""
ldap2 = self.api.Backend.ldap2
if host_has_service(api.env.ca_host, ldap2, "KRA"):
return api.env.ca_host
if api.env.host != api.env.ca_host:
if host_has_service(api.env.host, ldap2, "KRA"):
return api.env.host
host = select_any_master(ldap2, "KRA")
if host:
return host
else:
return api.env.ca_host
def get_client(self):
"""
Returns an authenticated KRA client to access KRA services.
Raises a generic exception if KRA is not enabled.
"""
if not self.api.Command.kra_is_enabled()['result']:
# TODO: replace this with a more specific exception
raise RuntimeError('KRA service is not enabled')
crypto = cryptoutil.NSSCryptoProvider(
paths.HTTPD_ALIAS_DIR,
password_file=paths.ALIAS_PWDFILE_TXT)
# TODO: obtain KRA host & port from IPA service list or point to KRA load balancer
# https://fedorahosted.org/freeipa/ticket/4557
connection = PKIConnection(
'https',
self.kra_host,
str(self.kra_port),
'kra')
connection.set_authentication_cert(paths.KRA_AGENT_PEM)
return KRAClient(connection, crypto)
api.register(kra)
class RestClient(Backend):
"""Simple Dogtag REST client to be subclassed by other backends.
This class is a context manager. Authenticated calls must be
executed in a ``with`` suite::
class ra_certprofile(RestClient):
path = 'profile'
...
api.register(ra_certprofile)
with api.Backend.ra_certprofile as profile_api:
# REST client is now logged in
profile_api.create_profile(...)
"""
path = None
@staticmethod
def _parse_dogtag_error(body):
try:
return pki.PKIException.from_json(json.loads(body))
except:
return None
def __init__(self, api):
if api.env.in_tree:
self.sec_dir = api.env.dot_ipa + os.sep + 'alias'
self.pwd_file = self.sec_dir + os.sep + '.pwd'
else:
self.sec_dir = paths.HTTPD_ALIAS_DIR
self.pwd_file = paths.ALIAS_PWDFILE_TXT
self.noise_file = self.sec_dir + os.sep + '.noise'
self.ipa_key_size = "2048"
self.ipa_certificate_nickname = "ipaCert"
self.ca_certificate_nickname = "caCert"
self._read_password()
super(RestClient, self).__init__(api)
# session cookie
self.override_port = None
self.cookie = None
def _read_password(self):
try:
with open(self.pwd_file) as f:
self.password = f.readline().strip()
except IOError:
self.password = ''
@cachedproperty
def ca_host(self):
"""
:return: host
as str
Select our CA host.
"""
ldap2 = self.api.Backend.ldap2
if host_has_service(api.env.ca_host, ldap2, "CA"):
return api.env.ca_host
if api.env.host != api.env.ca_host:
if host_has_service(api.env.host, ldap2, "CA"):
return api.env.host
host = select_any_master(ldap2)
if host:
return host
else:
return api.env.ca_host
def __enter__(self):
"""Log into the REST API"""
if self.cookie is not None:
return
status, resp_headers, resp_body = dogtag.https_request(
self.ca_host, self.override_port or self.env.ca_agent_port,
'/ca/rest/account/login',
self.sec_dir, self.password, self.ipa_certificate_nickname,
method='GET'
)
cookies = ipapython.cookie.Cookie.parse(resp_headers.get('set-cookie', ''))
if status != 200 or len(cookies) == 0:
raise errors.RemoteRetrieveError(reason=_('Failed to authenticate to CA REST API'))
self.cookie = str(cookies[0])
return self
def __exit__(self, exc_type, exc_value, traceback):
"""Log out of the REST API"""
dogtag.https_request(
self.ca_host, self.override_port or self.env.ca_agent_port,
'/ca/rest/account/logout',
self.sec_dir, self.password, self.ipa_certificate_nickname,
method='GET'
)
self.cookie = None
def _ssldo(self, method, path, headers=None, body=None):
"""
:param url: The URL to post to.
:param kw: Keyword arguments to encode into POST body.
:return: (http_status, http_headers, http_body)
as (integer, dict, str)
Perform an HTTPS request
"""
if self.cookie is None:
raise errors.RemoteRetrieveError(
reason=_("REST API is not logged in."))
headers = headers or {}
headers['Cookie'] = self.cookie
resource = os.path.join('/ca/rest', self.path, path)
# perform main request
status, resp_headers, resp_body = dogtag.https_request(
self.ca_host, self.override_port or self.env.ca_agent_port,
resource,
self.sec_dir, self.password, self.ipa_certificate_nickname,
method=method, headers=headers, body=body
)
if status < 200 or status >= 300:
explanation = self._parse_dogtag_error(resp_body) or ''
raise errors.RemoteRetrieveError(
reason=_('Non-2xx response from CA REST API: %(status)d. %(explanation)s')
% {'status': status, 'explanation': explanation}
)
return (status, resp_headers, resp_body)
class ra_certprofile(RestClient):
"""
Profile management backend plugin.
"""
path = 'profiles'
def create_profile(self, profile_data):
"""
Import the profile into Dogtag
"""
self._ssldo('POST', 'raw',
headers={
'Content-type': 'application/xml',
'Accept': 'application/json',
},
body=profile_data
)
def read_profile(self, profile_id):
"""
Read the profile configuration from Dogtag
"""
status, resp_headers, resp_body = self._ssldo(
'GET', profile_id + '/raw')
return resp_body
def update_profile(self, profile_id, profile_data):
"""
Update the profile configuration in Dogtag
"""
self._ssldo('PUT', profile_id + '/raw',
headers={
'Content-type': 'application/xml',
'Accept': 'application/json',
},
body=profile_data
)
def enable_profile(self, profile_id):
"""
Enable the profile in Dogtag
"""
self._ssldo('POST', profile_id + '?action=enable')
def disable_profile(self, profile_id):
"""
Enable the profile in Dogtag
"""
self._ssldo('POST', profile_id + '?action=disable')
def delete_profile(self, profile_id):
"""
Delete the profile from Dogtag
"""
self._ssldo('DELETE', profile_id, headers={'Accept': 'application/json'})
api.register(ra_certprofile)

View File

@@ -21,7 +21,7 @@
Joining an IPA domain
"""
import six
import krbV
from ipalib import api
from ipalib import Command, Str
@@ -29,8 +29,14 @@ from ipalib import errors
from ipalib import _
from ipaserver.install import installutils
if six.PY3:
unicode = str
def get_realm():
"""
Returns the default kerberos realm configured for this server.
"""
krbctx = krbV.default_context()
return unicode(krbctx.default_realm)
def validate_host(ugettext, cn):
@@ -60,7 +66,7 @@ class join(Command):
takes_options = (
Str('realm',
doc=_("The IPA realm"),
default_from=lambda: api.env.realm,
default_from=lambda: get_realm(),
autofill=True,
),
Str('nshardwareplatform?',

View File

@@ -30,12 +30,11 @@ Backend plugin for LDAP.
import os
import pwd
import krbV
import ldap as _ldap
from ipalib import krb_utils
from ipapython.dn import DN
from ipapython.ipaldap import (LDAPClient, AUTOBIND_AUTO, AUTOBIND_ENABLED,
AUTOBIND_DISABLED)
from ipapython.ipaldap import SASL_GSSAPI, IPASimpleLDAPObject, LDAPClient
try:
@@ -57,60 +56,35 @@ from ipalib.crud import CrudBackend
from ipalib.request import context
class ldap2(CrudBackend, LDAPClient):
class ldap2(LDAPClient, CrudBackend):
"""
LDAP Backend Take 2.
"""
def __init__(self, api, ldap_uri=None):
if ldap_uri is None:
ldap_uri = api.env.ldap_uri
def __init__(self, shared_instance=True, ldap_uri=None, base_dn=None,
schema=None):
try:
ldap_uri = ldap_uri or api.env.ldap_uri
except AttributeError:
ldap_uri = 'ldap://example.com'
force_schema_updates = api.env.context in ('installer', 'updates')
CrudBackend.__init__(self, shared_instance=shared_instance)
LDAPClient.__init__(self, ldap_uri)
CrudBackend.__init__(self, api)
LDAPClient.__init__(self, ldap_uri,
force_schema_updates=force_schema_updates)
try:
if base_dn is not None:
self.base_dn = DN(base_dn)
else:
self.base_dn = DN(api.env.basedn)
except AttributeError:
self.base_dn = DN()
self.__time_limit = None
self.__size_limit = None
@property
def time_limit(self):
if self.__time_limit is None:
return float(self.get_ipa_config().single_value.get(
'ipasearchtimelimit', 2))
return self.__time_limit
@time_limit.setter
def time_limit(self, val):
self.__time_limit = float(val)
@time_limit.deleter
def time_limit(self):
self.__time_limit = None
@property
def size_limit(self):
if self.__size_limit is None:
return int(self.get_ipa_config().single_value.get(
'ipasearchrecordslimit', 0))
return self.__size_limit
@size_limit.setter
def size_limit(self, val):
self.__size_limit = int(val)
@size_limit.deleter
def size_limit(self):
self.__size_limit = None
def _connect(self):
def _init_connection(self):
# Connectible.conn is a proxy to thread-local storage;
# do not set it
pass
def close(self):
def __del__(self):
if self.isconnected():
self.disconnect()
@@ -119,14 +93,13 @@ class ldap2(CrudBackend, LDAPClient):
def create_connection(self, ccache=None, bind_dn=None, bind_pw='',
tls_cacertfile=None, tls_certfile=None, tls_keyfile=None,
debug_level=0, autobind=AUTOBIND_AUTO, serverctrls=None,
clientctrls=None, time_limit=None, size_limit=None):
debug_level=0, autobind=False, serverctrls=None, clientctrls=None):
"""
Connect to LDAP server.
Keyword arguments:
ldapuri -- the LDAP server to connect to
ccache -- Kerberos ccache name
ccache -- Kerberos V5 ccache object or name
bind_dn -- dn used to bind to the server
bind_pw -- password used to bind to the server
debug_level -- LDAP debug level option
@@ -147,19 +120,15 @@ class ldap2(CrudBackend, LDAPClient):
if tls_keyfile is not None:
_ldap.set_option(_ldap.OPT_X_TLS_KEYFILE, tls_keyfile)
if time_limit is not None:
self.time_limit = time_limit
if size_limit is not None:
self.size_limit = size_limit
if debug_level:
_ldap.set_option(_ldap.OPT_DEBUG_LEVEL, debug_level)
client = LDAPClient(self.ldap_uri,
force_schema_updates=self._force_schema_updates)
conn = client._conn
with client.error_handler():
with self.error_handler():
force_updates = api.env.context in ('installer', 'updates')
conn = IPASimpleLDAPObject(
self.ldap_uri, force_schema_updates=force_updates)
if self.ldap_uri.startswith('ldapi://') and ccache:
conn.set_option(_ldap.OPT_HOST_NAME, api.env.host)
minssf = conn.get_option(_ldap.OPT_X_SASL_SSF_MIN)
maxssf = conn.get_option(_ldap.OPT_X_SASL_SSF_MAX)
# Always connect with at least an SSF of 56, confidentiality
@@ -169,62 +138,171 @@ class ldap2(CrudBackend, LDAPClient):
conn.set_option(_ldap.OPT_X_SASL_SSF_MIN, minssf)
if maxssf < minssf:
conn.set_option(_ldap.OPT_X_SASL_SSF_MAX, minssf)
if ccache is not None:
if isinstance(ccache, krbV.CCache):
principal = ccache.principal().name
# Get a fully qualified CCACHE name (schema+name)
# As we do not use the krbV.CCache object later,
# we can safely overwrite it
ccache = "%(type)s:%(name)s" % dict(type=ccache.type,
name=ccache.name)
else:
principal = krbV.CCache(name=ccache,
context=krbV.default_context()).principal().name
ldapi = self.ldap_uri.startswith('ldapi://')
if bind_pw:
client.simple_bind(bind_dn, bind_pw,
server_controls=serverctrls,
client_controls=clientctrls)
elif autobind != AUTOBIND_DISABLED and os.getegid() == 0 and ldapi:
try:
pw_name = pwd.getpwuid(os.geteuid()).pw_name
client.external_bind(pw_name,
server_controls=serverctrls,
client_controls=clientctrls)
except errors.NotFound:
if autobind == AUTOBIND_ENABLED:
# autobind was required and failed, raise
# exception that it failed
raise
else:
if ldapi:
with client.error_handler():
conn.set_option(_ldap.OPT_HOST_NAME, self.api.env.host)
if ccache is None:
os.environ.pop('KRB5CCNAME', None)
else:
os.environ['KRB5CCNAME'] = ccache
principal = krb_utils.get_principal(ccache_name=ccache)
client.gssapi_bind(server_controls=serverctrls,
client_controls=clientctrls)
setattr(context, 'principal', principal)
conn.sasl_interactive_bind_s(None, SASL_GSSAPI,
serverctrls=serverctrls,
clientctrls=clientctrls)
setattr(context, 'principal', principal)
else:
# no kerberos ccache, use simple bind or external sasl
if autobind:
pent = pwd.getpwuid(os.geteuid())
auth_tokens = _ldap.sasl.external(pent.pw_name)
conn.sasl_interactive_bind_s(None, auth_tokens,
serverctrls=serverctrls,
clientctrls=clientctrls)
else:
conn.simple_bind_s(bind_dn, bind_pw,
serverctrls=serverctrls,
clientctrls=clientctrls)
return conn
def destroy_connection(self):
"""Disconnect from LDAP server."""
try:
if self.conn is not None:
self.unbind()
except errors.PublicError:
self.conn.unbind_s()
except _ldap.LDAPError:
# ignore when trying to unbind multiple times
pass
del self.time_limit
del self.size_limit
def find_entries(self, filter=None, attrs_list=None, base_dn=None,
scope=_ldap.SCOPE_SUBTREE, time_limit=None,
size_limit=None, search_refs=False, paged_search=False):
def _get_limits():
"""Get configured global limits, caching them for more calls"""
if not _lims:
config = self.get_ipa_config()
_lims['time'] = config.get('ipasearchtimelimit', [None])[0]
_lims['size'] = config.get('ipasearchrecordslimit', [None])[0]
return _lims
_lims = {}
if time_limit is None:
time_limit = _get_limits()['time']
if size_limit is None:
size_limit = _get_limits()['size']
has_memberindirect = False
has_memberofindirect = False
if attrs_list:
new_attrs_list = []
for attr_name in attrs_list:
if attr_name == 'memberindirect':
has_memberindirect = True
elif attr_name == 'memberofindirect':
has_memberofindirect = True
else:
new_attrs_list.append(attr_name)
attrs_list = new_attrs_list
res, truncated = super(ldap2, self).find_entries(
filter=filter, attrs_list=attrs_list, base_dn=base_dn, scope=scope,
time_limit=time_limit, size_limit=size_limit,
search_refs=search_refs, paged_search=paged_search)
if has_memberindirect or has_memberofindirect:
# For the memberof searches, we want to apply the global limit
# if it's larger than the requested one, so decreasing limits on
# the individual query only affects the query itself.
# See https://fedorahosted.org/freeipa/ticket/4398
def _max_with_none(a, b):
"""Maximum of a and b, treating None as infinity"""
if a is None or b is None:
return None
else:
return max(a, b)
time_limit = _max_with_none(time_limit, _get_limits()['time'])
size_limit = _max_with_none(size_limit, _get_limits()['size'])
for entry in res:
if has_memberindirect:
self._process_memberindirect(
entry, time_limit=time_limit, size_limit=size_limit)
if has_memberofindirect:
self._process_memberofindirect(
entry, time_limit=time_limit, size_limit=size_limit)
return (res, truncated)
def _process_memberindirect(self, group_entry, time_limit=None,
size_limit=None):
filter = self.make_filter({'memberof': group_entry.dn})
try:
result, truncated = self.find_entries(
base_dn=self.api.env.basedn,
filter=filter,
attrs_list=['member'],
time_limit=time_limit,
size_limit=size_limit,
paged_search=True)
if truncated:
raise errors.LimitsExceeded()
except errors.NotFound:
result = []
indirect = set()
for entry in result:
indirect.update(entry.get('member', []))
indirect.difference_update(group_entry.get('member', []))
if indirect:
group_entry['memberindirect'] = list(indirect)
def _process_memberofindirect(self, entry, time_limit=None,
size_limit=None):
dn = entry.dn
filter = self.make_filter(
{'member': dn, 'memberuser': dn, 'memberhost': dn})
try:
result, truncated = self.find_entries(
base_dn=self.api.env.basedn,
filter=filter,
attrs_list=[''],
time_limit=time_limit,
size_limit=size_limit)
if truncated:
raise errors.LimitsExceeded()
except errors.NotFound:
result = []
direct = set()
indirect = set(entry.get('memberof', []))
for group_entry in result:
dn = group_entry.dn
if dn in indirect:
indirect.remove(dn)
direct.add(dn)
entry['memberof'] = list(direct)
if indirect:
entry['memberofindirect'] = list(indirect)
config_defaults = {'ipasearchtimelimit': [2], 'ipasearchrecordslimit': [0]}
def get_ipa_config(self, attrs_list=None):
"""Returns the IPA configuration entry (dn, entry_attrs)."""
dn = self.api.Object.config.get_dn()
dn = api.Object.config.get_dn()
assert isinstance(dn, DN)
try:
config_entry = getattr(context, 'config_entry')
if config_entry.conn.conn is self.conn:
if config_entry.conn is self.conn:
return config_entry
except AttributeError:
# Not in our context yet
@@ -239,7 +317,9 @@ class ldap2(CrudBackend, LDAPClient):
config_entry = entries[0]
except errors.NotFound:
config_entry = self.make_entry(dn)
for a in self.config_defaults:
if a not in config_entry:
config_entry[a] = self.config_defaults[a]
context.config_entry = config_entry
return config_entry
@@ -254,14 +334,12 @@ class ldap2(CrudBackend, LDAPClient):
"""
upg_dn = DN(('cn', 'UPG Definition'), ('cn', 'Definitions'), ('cn', 'Managed Entries'),
('cn', 'etc'), self.api.env.basedn)
('cn', 'etc'), api.env.basedn)
try:
with self.error_handler():
upg_entries = self.conn.search_s(str(upg_dn), _ldap.SCOPE_BASE,
attrlist=['*'])
upg_entries = self._convert_result(upg_entries)
except errors.NotFound:
upg_entries = self.conn.search_s(upg_dn, _ldap.SCOPE_BASE,
attrlist=['*'])
except _ldap.NO_SUCH_OBJECT:
upg_entries = None
if not upg_entries or 'originfilter' not in upg_entries[0]:
raise errors.ACIError(info=_(
@@ -281,7 +359,7 @@ class ldap2(CrudBackend, LDAPClient):
principal = getattr(context, 'principal')
entry = self.find_entry_by_attr("krbprincipalname", principal,
"krbPrincipalAux", base_dn=self.api.env.basedn)
"krbPrincipalAux", base_dn=api.env.basedn)
sctrl = [GetEffectiveRightsControl(True, "dn: " + str(entry.dn))]
self.conn.set_option(_ldap.OPT_SERVER_CONTROLS, sctrl)
try:
@@ -370,15 +448,14 @@ class ldap2(CrudBackend, LDAPClient):
pw = old_pass
if (otp):
pw = old_pass+otp
with LDAPClient(self.ldap_uri, force_schema_updates=False) as conn:
conn.simple_bind(dn, pw)
conn.unbind()
with self.error_handler():
conn = IPASimpleLDAPObject(
self.ldap_uri, force_schema_updates=False)
conn.simple_bind_s(dn, pw)
conn.unbind_s()
with self.error_handler():
old_pass = self.encode(old_pass)
new_pass = self.encode(new_pass)
self.conn.passwd_s(str(dn), old_pass, new_pass)
self.conn.passwd_s(dn, old_pass, new_pass)
def add_entry_to_group(self, dn, group_dn, member_attr='member', allow_same=False):
"""
@@ -410,9 +487,7 @@ class ldap2(CrudBackend, LDAPClient):
# update group entry
try:
with self.error_handler():
modlist = [(a, self.encode(b), self.encode(c))
for a, b, c in modlist]
self.conn.modify_s(str(group_dn), modlist)
self.conn.modify_s(group_dn, modlist)
except errors.DatabaseError:
raise errors.AlreadyGroupMember()
@@ -432,9 +507,7 @@ class ldap2(CrudBackend, LDAPClient):
# update group entry
try:
with self.error_handler():
modlist = [(a, self.encode(b), self.encode(c))
for a, b, c in modlist]
self.conn.modify_s(str(group_dn), modlist)
self.conn.modify_s(group_dn, modlist)
except errors.MidairCollision:
raise errors.NotGroupMember()
@@ -488,7 +561,7 @@ class ldap2(CrudBackend, LDAPClient):
(_ldap.MOD_REPLACE, 'krblastpwdchange', None)]
with self.error_handler():
self.conn.modify_s(str(dn), mod)
self.conn.modify_s(dn, mod)
# CrudBackend methods

Binary file not shown.

View File

@@ -41,14 +41,14 @@ class rabase(Backend):
"""
Request Authority backend plugin.
"""
def __init__(self, api):
def __init__(self):
if api.env.in_tree:
self.sec_dir = api.env.dot_ipa + os.sep + 'alias'
self.pwd_file = self.sec_dir + os.sep + '.pwd'
else:
self.sec_dir = paths.HTTPD_ALIAS_DIR
self.pwd_file = paths.ALIAS_PWDFILE_TXT
super(rabase, self).__init__(api)
super(rabase, self).__init__()
def check_request_status(self, request_id):
@@ -67,12 +67,11 @@ class rabase(Backend):
"""
raise errors.NotImplementedError(name='%s.get_certificate' % self.name)
def request_certificate(self, csr, profile_id, request_type='pkcs10'):
def request_certificate(self, csr, request_type='pkcs10'):
"""
Submit certificate signing request.
:param csr: The certificate signing request.
:param profile_id: Profile to use for this request.
:param request_type: The request type (defaults to ``'pkcs10'``).
"""
raise errors.NotImplementedError(name='%s.request_certificate' % self.name)