262 lines
8.6 KiB
Python
262 lines
8.6 KiB
Python
# Copyright 2016 Canonical Ltd
|
|
#
|
|
# 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.
|
|
|
|
import json
|
|
import os
|
|
import os.path
|
|
import shutil
|
|
import subprocess
|
|
|
|
import charmhelpers.core.hookenv as hookenv
|
|
import charmhelpers.core.host as ch_core_host
|
|
|
|
import charms_openstack.adapters
|
|
import charms_openstack.charm
|
|
|
|
|
|
SOFTHSM2_UTIL_CMD = "/usr/bin/softhsm2-util"
|
|
TOKEN_STORE = "/var/lib/softhsm/tokens/"
|
|
SOFTHSM2_CONF = "/etc/softhsm/softhsm2.conf"
|
|
SOFTHSM2_LIB_PATH = "/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so"
|
|
PIN_LENGTH = 32
|
|
BARBICAN_TOKEN_LABEL = "barbican_token"
|
|
STORED_PINS_FILE = "/var/lib/softhsm/stored_pins.txt"
|
|
|
|
|
|
def install():
|
|
"""Use the singleton from the BarbicanSoftHSMCharm to install the packages
|
|
on the unit
|
|
"""
|
|
BarbicanSoftHSMCharm.singleton.install()
|
|
|
|
|
|
def on_hsm_connected(hsm):
|
|
"""When SoftHSM connects to Barbican, configure Barbican with the
|
|
information necessary to configure the plugin.
|
|
|
|
:param hsm: the hsm relation object
|
|
"""
|
|
BarbicanSoftHSMCharm.singleton.on_hsm_connected(hsm)
|
|
|
|
|
|
def assess_status():
|
|
"""Call the charm assess_status function"""
|
|
BarbicanSoftHSMCharm.singleton.assess_status()
|
|
|
|
|
|
class BarbicanSoftHSMCharm(charms_openstack.charm.OpenStackCharm):
|
|
|
|
service_name = 'barbican-softhsm'
|
|
name = 'softhsm'
|
|
release = 'mitaka'
|
|
|
|
# Packages that the service needs installed
|
|
packages = ['softhsm2']
|
|
|
|
# Standard interface adapters class to use.
|
|
adapters_class = charms_openstack.adapters.OpenStackRelationAdapters
|
|
|
|
def install(self):
|
|
"""Perform the normal charm install, and then kick off setting up the
|
|
barbican_token in the softhsm2 token store.
|
|
"""
|
|
super(BarbicanSoftHSMCharm, self).install()
|
|
# now add the barbican user to the softhsm group so that the
|
|
# barbican-worker can access the softhsm2.conf file.
|
|
ch_core_host.add_user_to_group('barbican', 'softhsm')
|
|
self.setup_token_store()
|
|
hookenv.status_set(
|
|
'waiting', 'Charm installed and token store configured')
|
|
|
|
def setup_token_store(self):
|
|
"""Set up the token store for barbican to use, create a pin and
|
|
user_pin and store those details locally so that they can be used when
|
|
Barbican connects.
|
|
|
|
Performs as needed:
|
|
|
|
softhsm2-util --init-token --free --label "barbican_token" --pin <pin>
|
|
--so-pin <so-pin>
|
|
|
|
The <pin> and <so-pin> are generated randomly and saved to a
|
|
configuration file.
|
|
|
|
If the <pin> and <so-pin> configuration file don't exist, then the
|
|
token directory is deleted and re-initialised.
|
|
|
|
Thus if we are upgrading a charm, the charm checks to see if it has
|
|
already been run on this host, and if so, doesn't re-initialise the
|
|
token store, otherwise the token store is re-initialised.
|
|
|
|
The configuration file for the softhsm2 library is also written.
|
|
"""
|
|
# see if the <pin> and <so_pin> exist?
|
|
pin, so_pin = read_pins_from_store()
|
|
if pin is not None:
|
|
# return as the token store is already set up
|
|
return
|
|
# see if the token directory exists - if so, delete it.
|
|
if os.path.exists(TOKEN_STORE):
|
|
if os.path.isdir(TOKEN_STORE):
|
|
shutil.rmtree(TOKEN_STORE)
|
|
else:
|
|
os.remove(TOKEN_STORE)
|
|
os.makedirs(TOKEN_STORE)
|
|
# We need the token store to be 1777 so that whoever creates a token
|
|
# can also gain access to it - the token will be created by the
|
|
# barbican user.
|
|
os.chmod(TOKEN_STORE, 0o1777)
|
|
# now create the token store
|
|
pin = ch_core_host.pwgen(PIN_LENGTH)
|
|
so_pin = ch_core_host.pwgen(PIN_LENGTH)
|
|
write_pins_to_store(pin, so_pin)
|
|
cmd = [
|
|
'sudo', '-u', 'barbican',
|
|
SOFTHSM2_UTIL_CMD,
|
|
'--init-token', '--free',
|
|
'--label', BARBICAN_TOKEN_LABEL,
|
|
'--pin', pin,
|
|
'--so-pin', so_pin]
|
|
subprocess.check_call(cmd)
|
|
hookenv.log("Initialised token store.")
|
|
|
|
def on_hsm_connected(self, hsm):
|
|
"""Called when the hsm interface becomes connected. This means the
|
|
plugin has connected to the principal Barbican charm.
|
|
|
|
In order for the Barbican charm to use this plugin (softhsm2) the
|
|
plugin needs to provide a PKCS#11 libary for barbican to access, a
|
|
password to access the token and a slot_id for the token.
|
|
|
|
This sets the plugin_data on the hsm relation for the Barbican charm to
|
|
pick up.
|
|
|
|
:param hsm: a BarbicanProvides instance for the relation.
|
|
:raises RuntimeError: if the token_store can't be setup - which is
|
|
FATAL.
|
|
"""
|
|
hookenv.log("Setting plugin name to softhsm2", level=hookenv.DEBUG)
|
|
hsm.set_name('softhsm2')
|
|
pin, so_pin = read_pins_from_store()
|
|
if pin is None:
|
|
self.setup_token_store()
|
|
pin, so_pin = read_pins_from_store()
|
|
if pin is None:
|
|
hookenv.status_set('error', "Couldn't set up the token store?")
|
|
raise RuntimeError(
|
|
"BarbicanSoftHSMCharm.setup_token_store() failed?")
|
|
slot_id = read_slot_id(BARBICAN_TOKEN_LABEL)
|
|
if slot_id is None:
|
|
raise RuntimeError("No {} slot in token store?"
|
|
.format(BARBICAN_TOKEN_LABEL))
|
|
plugin_data = {
|
|
"library_path": SOFTHSM2_LIB_PATH,
|
|
"login": pin,
|
|
"slot_id": slot_id
|
|
}
|
|
hsm.set_plugin_data(plugin_data)
|
|
|
|
|
|
def read_pins_from_store():
|
|
"""Read the pin and so_pin from the STORED_PINS_FILE file so that they can
|
|
be retrieved later.
|
|
|
|
The pins are stored in the file with 600 permissions, with the following
|
|
JSON format:
|
|
|
|
{
|
|
'pin': <pin string>,
|
|
'so_pin': <so_pin string>
|
|
}
|
|
|
|
:returns (pin, so_pin): the pins from the store or None, None
|
|
"""
|
|
try:
|
|
with open(STORED_PINS_FILE, 'r') as f:
|
|
o = json.load(f)
|
|
pin = o['pin']
|
|
so_pin = o['so_pin']
|
|
return pin, so_pin
|
|
except Exception:
|
|
return None, None
|
|
|
|
|
|
def write_pins_to_store(pin, so_pin):
|
|
"""Write the pin and so_pin to the STORED_PINS_FILE file so that they can
|
|
be retrieved later.
|
|
|
|
The pins are stored in the file with 600 permissions, with the following
|
|
JSON format:
|
|
|
|
{
|
|
'pin': <pin string>,
|
|
'so_pin': <so_pin string>
|
|
}
|
|
|
|
:param pin: string to store
|
|
:param so_pin: string to store
|
|
:raises OSError: If the file couldn't be written.
|
|
:returns None:
|
|
"""
|
|
try:
|
|
with os.fdopen(os.open(STORED_PINS_FILE,
|
|
os.O_WRONLY | os.O_CREAT,
|
|
0o600), 'w') as f:
|
|
json.dump({'pin': pin, 'so_pin': so_pin}, f)
|
|
except OSError as e:
|
|
hookenv.log("Couldn't write pins file: {}".format(str(e)))
|
|
|
|
|
|
def read_slot_id(label):
|
|
"""Read the slot id for the `label` slot.
|
|
|
|
The format of the slot from 'softhsm2-util --show-slots' is:
|
|
|
|
Available slots:
|
|
Slot 0
|
|
Slot info:
|
|
Description: SoftHSM slot 0
|
|
Manufacturer ID: SoftHSM project
|
|
Hardware version: 2.0
|
|
Firmware version: 2.0
|
|
Token present: yes
|
|
Token info:
|
|
Manufacturer ID: SoftHSM project
|
|
Model: SoftHSM v2
|
|
Hardware version: 2.0
|
|
Firmware version: 2.0
|
|
Serial number: 02ae3171143498e7
|
|
Initialized: yes
|
|
User PIN init.: yes
|
|
Label: barbican_token
|
|
|
|
The function reads the text and then looks for the 'Label:' field, to
|
|
recover the Slot number.
|
|
|
|
:param label: string representing the slot to look for
|
|
:returns: slot number as String.
|
|
"""
|
|
cmd = [SOFTHSM2_UTIL_CMD, '--show-slots']
|
|
results = subprocess.check_output(cmd)
|
|
lines = results.decode().split("\n")
|
|
slot = None
|
|
for line in lines:
|
|
if line.startswith("Slot "):
|
|
slot = line[5:]
|
|
if (line.find('Label:') >= 0 and
|
|
line.find(label) >= 0 and
|
|
slot is not None):
|
|
return slot
|
|
return None
|