mirror of
https://github.com/xcat2/confluent.git
synced 2026-08-03 07:57:02 +00:00
Create and maintain the TLS CA as the confluent service account
The CA database under /etc/confluent/tls/ca is typically created by a root context such as osdeploy initialize -t, but the confluent service runs as the owner of /etc/confluent, and openssl ca rewrites the database (index, serial) as the invoking user on every issuance. Certificate issuance through the running service (e.g. the /self/tlscert deployment API) then fails on the root-owned database until packaging happens to repair the ownership. Run the CA creation (full CA and the currently unused simple CA variant) and the openssl ca invocation under normalize_uid, the convention already used when publishing the CA certificate. The issued certificate is staged through a temporary file since the destination may only be writable by the invoking user, e.g. the web server certificate paths. Existing root-owned CA databases are repaired by packaging or manually via: chown -R --reference=/etc/confluent /etc/confluent/tls
This commit is contained in:
@@ -207,85 +207,98 @@ def substitute_cfg(setting, key, val, newval, cfgfile, line):
|
||||
return False
|
||||
|
||||
async def create_full_ca(certout):
|
||||
mkdirp('/etc/confluent/tls/ca/private')
|
||||
keyout = '/etc/confluent/tls/ca/private/cakey.pem'
|
||||
csrout = '/etc/confluent/tls/ca/ca.csr'
|
||||
mkdirp('/etc/confluent/tls/ca/newcerts')
|
||||
with open('/etc/confluent/tls/ca/index.txt', 'w') as idx:
|
||||
pass
|
||||
with open('/etc/confluent/tls/ca/index.txt.attr', 'w') as idx:
|
||||
idx.write('unique_subject = no')
|
||||
with open('/etc/confluent/tls/ca/serial', 'w') as srl:
|
||||
srl.write('01')
|
||||
sslcfg = get_openssl_conf_location()
|
||||
newcfg = '/etc/confluent/tls/ca/openssl.cfg'
|
||||
settings = {
|
||||
'dir': '/etc/confluent/tls/ca',
|
||||
'certificate': '$dir/cacert.pem',
|
||||
'private_key': '$dir/private/cakey.pem',
|
||||
'countryName': 'optional',
|
||||
'stateOrProvinceName': 'optional',
|
||||
'organizationName': 'optional',
|
||||
}
|
||||
subj = '/CN=Confluent TLS Certificate authority ({0})'.format(socket.gethostname())
|
||||
if len(subj) > 68:
|
||||
subj = subj[:68]
|
||||
with open(sslcfg, 'r') as cfgin:
|
||||
with open(newcfg, 'w') as cfgfile:
|
||||
for line in cfgin.readlines():
|
||||
cfg = line.split('#')[0]
|
||||
if '=' in cfg:
|
||||
key, val = cfg.split('=', 1)
|
||||
for stg in settings:
|
||||
if substitute_cfg(stg, key, val, settings[stg], cfgfile, line):
|
||||
break
|
||||
else:
|
||||
cfgfile.write(line.strip() + '\n')
|
||||
continue
|
||||
cfgfile.write(line.strip() + '\n')
|
||||
cfgfile.write('\n[CACert]\nbasicConstraints = critical,CA:true\nkeyUsage = critical,keyCertSign,cRLSign\n[ca_confluent]\n')
|
||||
await util.check_call(
|
||||
'openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout)
|
||||
await util.check_call(
|
||||
'openssl', 'req', '-new', '-key', keyout, '-out', csrout, '-subj', subj)
|
||||
await util.check_call(
|
||||
'openssl', 'ca', '-config', newcfg, '-batch', '-selfsign',
|
||||
'-extensions', 'CACert', '-extfile', newcfg,
|
||||
'-notext', '-md', 'sha384', '-startdate',
|
||||
'19700101010101Z', '-enddate', '21000101010101Z', '-keyfile',
|
||||
keyout, '-out', '/etc/confluent/tls/ca/cacert.pem', '-in', csrout
|
||||
)
|
||||
shutil.copy2('/etc/confluent/tls/ca/cacert.pem', certout)
|
||||
# The CA is used by the confluent service, which runs as the owner of
|
||||
# /etc/confluent rather than root; create the CA material as that user
|
||||
# so the service can use the database for issuing certificates
|
||||
ouid = normalize_uid()
|
||||
try:
|
||||
mkdirp('/etc/confluent/tls/ca/private')
|
||||
keyout = '/etc/confluent/tls/ca/private/cakey.pem'
|
||||
csrout = '/etc/confluent/tls/ca/ca.csr'
|
||||
mkdirp('/etc/confluent/tls/ca/newcerts')
|
||||
with open('/etc/confluent/tls/ca/index.txt', 'w') as idx:
|
||||
pass
|
||||
with open('/etc/confluent/tls/ca/index.txt.attr', 'w') as idx:
|
||||
idx.write('unique_subject = no')
|
||||
with open('/etc/confluent/tls/ca/serial', 'w') as srl:
|
||||
srl.write('01')
|
||||
sslcfg = get_openssl_conf_location()
|
||||
newcfg = '/etc/confluent/tls/ca/openssl.cfg'
|
||||
settings = {
|
||||
'dir': '/etc/confluent/tls/ca',
|
||||
'certificate': '$dir/cacert.pem',
|
||||
'private_key': '$dir/private/cakey.pem',
|
||||
'countryName': 'optional',
|
||||
'stateOrProvinceName': 'optional',
|
||||
'organizationName': 'optional',
|
||||
}
|
||||
subj = '/CN=Confluent TLS Certificate authority ({0})'.format(socket.gethostname())
|
||||
if len(subj) > 68:
|
||||
subj = subj[:68]
|
||||
with open(sslcfg, 'r') as cfgin:
|
||||
with open(newcfg, 'w') as cfgfile:
|
||||
for line in cfgin.readlines():
|
||||
cfg = line.split('#')[0]
|
||||
if '=' in cfg:
|
||||
key, val = cfg.split('=', 1)
|
||||
for stg in settings:
|
||||
if substitute_cfg(stg, key, val, settings[stg], cfgfile, line):
|
||||
break
|
||||
else:
|
||||
cfgfile.write(line.strip() + '\n')
|
||||
continue
|
||||
cfgfile.write(line.strip() + '\n')
|
||||
cfgfile.write('\n[CACert]\nbasicConstraints = critical,CA:true\nkeyUsage = critical,keyCertSign,cRLSign\n[ca_confluent]\n')
|
||||
await util.check_call(
|
||||
'openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout)
|
||||
await util.check_call(
|
||||
'openssl', 'req', '-new', '-key', keyout, '-out', csrout, '-subj', subj)
|
||||
await util.check_call(
|
||||
'openssl', 'ca', '-config', newcfg, '-batch', '-selfsign',
|
||||
'-extensions', 'CACert', '-extfile', newcfg,
|
||||
'-notext', '-md', 'sha384', '-startdate',
|
||||
'19700101010101Z', '-enddate', '21000101010101Z', '-keyfile',
|
||||
keyout, '-out', '/etc/confluent/tls/ca/cacert.pem', '-in', csrout
|
||||
)
|
||||
shutil.copy2('/etc/confluent/tls/ca/cacert.pem', certout)
|
||||
finally:
|
||||
os.seteuid(ouid)
|
||||
#openssl ca -config openssl.cnf -selfsign -keyfile cakey.pem -startdate 20150214120000Z -enddate 20160214120000Z
|
||||
#20160107071311Z -enddate 20170106071311Z
|
||||
|
||||
async def create_simple_ca(keyout, certout):
|
||||
# As with create_full_ca, the CA material must be owned by the owner
|
||||
# of /etc/confluent for use by the confluent service
|
||||
ouid = normalize_uid()
|
||||
try:
|
||||
os.makedirs('/etc/confluent/tls')
|
||||
except OSError as e:
|
||||
if e.errno != 17:
|
||||
raise
|
||||
sslcfg = get_openssl_conf_location()
|
||||
tmphdl, tmpconfig = tempfile.mkstemp()
|
||||
os.close(tmphdl)
|
||||
shutil.copy2(sslcfg, tmpconfig)
|
||||
await util.check_call(
|
||||
'openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout)
|
||||
try:
|
||||
subj = '/CN=Confluent TLS Certificate authority ({0})'.format(socket.gethostname())
|
||||
if len(subj) > 68:
|
||||
subj = subj[:68]
|
||||
with open(tmpconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\n[CACert]\nbasicConstraints = critical,CA:true\n')
|
||||
try:
|
||||
os.makedirs('/etc/confluent/tls')
|
||||
except OSError as e:
|
||||
if e.errno != 17:
|
||||
raise
|
||||
sslcfg = get_openssl_conf_location()
|
||||
tmphdl, tmpconfig = tempfile.mkstemp()
|
||||
os.close(tmphdl)
|
||||
shutil.copy2(sslcfg, tmpconfig)
|
||||
await util.check_call(
|
||||
'openssl', 'req', '-new', '-x509', '-key', keyout, '-days',
|
||||
'27300', '-out', certout, '-subj', subj,
|
||||
'-extensions', 'CACert', '-config', tmpconfig
|
||||
)
|
||||
'openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout)
|
||||
try:
|
||||
subj = '/CN=Confluent TLS Certificate authority ({0})'.format(socket.gethostname())
|
||||
if len(subj) > 68:
|
||||
subj = subj[:68]
|
||||
with open(tmpconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\n[CACert]\nbasicConstraints = critical,CA:true\n')
|
||||
await util.check_call(
|
||||
'openssl', 'req', '-new', '-x509', '-key', keyout, '-days',
|
||||
'27300', '-out', certout, '-subj', subj,
|
||||
'-extensions', 'CACert', '-config', tmpconfig
|
||||
)
|
||||
finally:
|
||||
os.remove(tmpconfig)
|
||||
finally:
|
||||
os.remove(tmpconfig)
|
||||
os.seteuid(ouid)
|
||||
|
||||
async def create_certificate(keyout=None, certout=None, csrfile=None, subj=None, san=None, backdate=True, days=None):
|
||||
now_utc = datetime.datetime.now(datetime.timezone.utc)
|
||||
@@ -413,12 +426,30 @@ async def create_certificate(keyout=None, certout=None, csrfile=None, subj=None,
|
||||
shutil.copy2(cacfgfile, tmpcafile)
|
||||
os.close(tmphdl)
|
||||
cacfgfile = tmpcafile
|
||||
await util.check_call(
|
||||
'openssl', 'ca', '-config', cacfgfile, '-rand_serial',
|
||||
'-in', csrfile, '-out', certout, '-batch', '-notext',
|
||||
'-startdate', startdate, '-enddate', enddate, '-md', 'sha384',
|
||||
'-extfile', extconfig, '-subj', subj
|
||||
)
|
||||
os.chmod(cacfgfile, 0o644)
|
||||
os.chmod(csrfile, 0o644)
|
||||
# openssl ca rewrites the CA database (index, serial) as the
|
||||
# invoking user; run it as the owner of /etc/confluent so the
|
||||
# database remains usable by the confluent service. The chmodded
|
||||
# temporary inputs hold no secrets, and the certificate is
|
||||
# written to a temporary path first, as certout may only be
|
||||
# writable by the original user (e.g. a web server certificate
|
||||
# path during osdeploy initialize -t)
|
||||
os.chmod(extconfig, 0o644)
|
||||
ouid = normalize_uid()
|
||||
try:
|
||||
tmphdl, tmpcertout = tempfile.mkstemp()
|
||||
os.close(tmphdl)
|
||||
await util.check_call(
|
||||
'openssl', 'ca', '-config', cacfgfile, '-rand_serial',
|
||||
'-in', csrfile, '-out', tmpcertout, '-batch', '-notext',
|
||||
'-startdate', startdate, '-enddate', enddate, '-md', 'sha384',
|
||||
'-extfile', extconfig, '-subj', subj
|
||||
)
|
||||
finally:
|
||||
os.seteuid(ouid)
|
||||
shutil.copy(tmpcertout, certout)
|
||||
os.remove(tmpcertout)
|
||||
for keycopy in tlsmateriallocation.get('keys', []):
|
||||
if keycopy != keyout:
|
||||
shutil.copy2(keyout, keycopy)
|
||||
|
||||
Reference in New Issue
Block a user