#!/usr/bin/env bash

# Install and start **Barbican** service

# To enable a minimal set of Barbican features, add the following to localrc:
#   enable_service barbican-svc barbican-retry barbican-keystone-listener
#
# Dependencies:
# - functions
# - OS_AUTH_URL for auth in api
# - DEST set to the destination directory
# - SERVICE_PROTOCOL, SERVICE_HOST to define the API endpoints
# - SERVICE_PASSWORD, SERVICE_PROJECT_NAME for auth in api
# - STACK_USER service user

# stack.sh
# ---------
# install_barbican
# configure_barbican
# init_barbican
# start_barbican
# stop_barbican
# cleanup_barbican

# Save trace setting
XTRACE=$(set +o | grep xtrace)
set +o xtrace

# PyKMIP configuration
PYKMIP_SERVER_KEY=${PYKMIP_SERVER_KEY:-$INT_CA_DIR/private/pykmip-server.key}
PYKMIP_SERVER_CERT=${PYKMIP_SERVER_CERT:-$INT_CA_DIR/pykmip-server.crt}
PYKMIP_CLIENT_KEY=${PYKMIP_CLIENT_KEY:-$INT_CA_DIR/private/pykmip-client.key}
PYKMIP_CLIENT_CERT=${PYKMIP_CLIENT_CERT:-$INT_CA_DIR/pykmip-client.crt}
PYKMIP_CA_PATH=${PYKMIP_CA_PATH:-$INT_CA_DIR/ca-chain.pem}

# Functions
# ---------

# TODO(john-wood-w) These 'magic' functions are called by devstack to enable
# a given service (so the name between 'is_' and '_enabled'). Currently the
# Zuul infra gate configuration (at https://github.com/openstack-infra/project-config/blob/master/jenkins/jobs/barbican.yaml)
# only enables the 'barbican' service. So the two functions below, for the two
# services we wish to run, have to key off of that lone 'barbican' selection.
# Once the Zuul config is updated to add these two services properly, then
# these functions should be replaced by the single method below.
# !!!! Special thanks to rm_work for figuring this out !!!!
function is_barbican-retry_enabled {
    [[ ,${ENABLED_SERVICES} =~ ,"barbican" ]] && return 0
}

function is_barbican-svc_enabled {
    [[ ,${ENABLED_SERVICES} =~ ,"barbican" ]] && return 0
}

function is_barbican-keystone-listener_enabled {
    [[ ,${ENABLED_SERVICES} =~ ,"barbican" ]] && return 0
}

# TODO(john-wood-w) Replace the above two functions with the one below once
# Zuul is update per above.
## Test if any Barbican services are enabled
## is_barbican_enabled
#function is_barbican_enabled {
#    [[ ,${ENABLED_SERVICES} =~ ,"barbican-" ]] && return 0
#    return 1
#}

# cleanup_barbican - Remove residual data files, anything left over from previous
# runs that a clean run would need to clean up
function cleanup_barbican {
    if is_service_enabled barbican-vault; then
        # Kill the vault process, screen session and remove the generated files
        # during installation.
        local session_name="barbican_vault"
        local vault_token_file="${BARBICAN_DIR}/vault_root_token_id"
        existing_ses=$(screen -ls | grep ${session_name} | awk '{print $1}')
        if [[ -n "${existing_ses}" ]]; then
            screen -S ${existing_ses} -X quit
        fi
        sudo pkill -f -9 "vault server"
        sudo rm -f ${vault_token_file} vault.log
    fi
}

# configure_barbicanclient - Set config files, create data dirs, etc
function configure_barbicanclient {
    setup_dev_lib "python-barbicanclient"
}

# configure_dogtag_plugin - Change config to use dogtag plugin
function configure_dogtag_plugin {
    sudo openssl pkcs12 -in /root/.dogtag/pki-tomcat/ca_admin_cert.p12 -passin pass:PASSWORD -out $BARBICAN_CONF_DIR/kra_admin_cert.pem -nodes
    sudo chown $USER $BARBICAN_CONF_DIR/kra_admin_cert.pem
    iniset $BARBICAN_CONF dogtag_plugin dogtag_port 8373
    iniset $BARBICAN_CONF dogtag_plugin pem_path "$BARBICAN_CONF_DIR/kra_admin_cert.pem"
    iniset $BARBICAN_CONF dogtag_plugin dogtag_host localhost
    iniset $BARBICAN_CONF dogtag_plugin nss_db_path '/etc/barbican/alias'
    iniset $BARBICAN_CONF dogtag_plugin nss_db_path_ca '/etc/barbican/alias-ca'
    iniset $BARBICAN_CONF dogtag_plugin nss_password 'password123'
    iniset $BARBICAN_CONF dogtag_plugin simple_cmc_profile 'caOtherCert'
    iniset $BARBICAN_CONF dogtag_plugin ca_expiration_time 1
    iniset $BARBICAN_CONF dogtag_plugin plugin_working_dir '/etc/barbican/dogtag'
    iniset $BARBICAN_CONF secretstore enabled_secretstore_plugins dogtag_crypto
    iniset $BARBICAN_CONF certificate enabled_certificate_plugins dogtag
}

# configure_barbican - Set config files, create data dirs, etc
function configure_barbican {
    setup_develop $BARBICAN_DIR

    [ ! -d $BARBICAN_CONF_DIR ] && sudo mkdir -m 755 -p $BARBICAN_CONF_DIR
    sudo chown $USER $BARBICAN_CONF_DIR

    [ ! -d $BARBICAN_API_LOG_DIR ] &&  sudo mkdir -m 755 -p $BARBICAN_API_LOG_DIR
    sudo chown $USER $BARBICAN_API_LOG_DIR

    [ ! -d $BARBICAN_CONF_DIR ] && sudo mkdir -m 755 -p $BARBICAN_CONF_DIR
    sudo chown $USER $BARBICAN_CONF_DIR

    # Copy the barbican config files to the config dir
    cp $BARBICAN_DIR/etc/barbican/barbican-api-paste.ini $BARBICAN_CONF_DIR
    cp -R $BARBICAN_DIR/etc/barbican/vassals $BARBICAN_CONF_DIR

    # Copy functional test config
    cp $BARBICAN_DIR/etc/barbican/barbican-functional.conf $BARBICAN_CONF_DIR

    # Enable DEBUG
    iniset $BARBICAN_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL

    # Set the host_href
    iniset $BARBICAN_CONF DEFAULT host_href "$BARBICAN_HOST_HREF"

    # Set the log file location
    iniset $BARBICAN_CONF DEFAULT log_file "$BARBICAN_API_LOG_DIR/barbican.log"

    # Enable logging to stderr to have log also in the screen window
    iniset $BARBICAN_CONF DEFAULT use_stderr True

    # Format logging
    if [ "$LOG_COLOR" == "True" ] && [ "$SYSLOG" == "False" ]; then
        setup_colorized_logging $BARBICAN_CONF DEFAULT project user
    fi

    # Set the database connection url
    iniset $BARBICAN_CONF DEFAULT sql_connection `database_connection_url barbican`

    # Disable auto-migration when deploying Barbican
    iniset $BARBICAN_CONF DEFAULT db_auto_create False

    # Increase default request buffer size, keystone auth PKI tokens can be very long
    iniset $BARBICAN_CONF_DIR/vassals/barbican-api.ini uwsgi buffer-size 65535

    # Rabbit settings
    if is_service_enabled rabbit; then
        iniset $BARBICAN_CONF DEFAULT transport_url rabbit://$RABBIT_USERID:$RABBIT_PASSWORD@$RABBIT_HOST:5672
    else
        echo_summary "Barbican requires that the RabbitMQ service is enabled"
    fi

    write_uwsgi_config "$BARBICAN_UWSGI_CONF" "$BARBICAN_WSGI" "/key-manager"
    ## Set up keystone

    # Turn on the middleware
    iniset $BARBICAN_PASTE_CONF 'pipeline:barbican_api' pipeline 'barbican-api-keystone'

    # Set the keystone parameters
    configure_keystone_authtoken_middleware $BARBICAN_CONF barbican

    # Enable the keystone listener
    iniset $BARBICAN_CONF keystone_notifications enable True
    iniset $BARBICAN_CONF keystone_notifications control_exchange 'keystone'
}

# init_barbican - Initialize etc.
function init_barbican {
    recreate_database barbican utf8

    $BARBICAN_BIN_DIR/barbican-manage db upgrade -v head
}

# install_barbican - Collect source and prepare
function install_barbican {
    # Install package requirements
    if is_fedora; then
        install_package sqlite-devel openldap-devel
    fi
    # TODO(ravips): We need this until barbican gets into devstack
    setup_develop $BARBICAN_DIR
    pip_install 'uwsgi'
}

# install_barbicanclient - Collect source and prepare
function install_barbicanclient {
    if use_library_from_git "python-barbicanclient"; then
        git_clone_by_name "python-barbicanclient"
        setup_dev_lib "python-barbicanclient"
    fi
}

# start_barbican - Start running processes, including screen
function start_barbican {
    # Start the Barbican service up.
    run_process barbican-svc "$BARBICAN_BIN_DIR/uwsgi --ini $BARBICAN_UWSGI_CONF"

    # Pause while the barbican-svc populates the database, otherwise the retry
    # service below might try to do this at the same time, leading to race
    # conditions.
    sleep 10

    # Start the retry scheduler server up.
    run_process barbican-retry "$BARBICAN_BIN_DIR/barbican-retry --config-file=$BARBICAN_CONF_DIR/barbican.conf"

    # Start the barbican-keystone-listener
    run_process barbican-keystone-listener "$BARBICAN_BIN_DIR/barbican-keystone-listener --config-file=$BARBICAN_CONF_DIR/barbican.conf"
}

# stop_barbican - Stop running processes
function stop_barbican {
    # This will eventually be refactored to work like
    # Solum and Manila (script to kick off a wsgiref server)
    # For now, this will stop uWSGI rather than have it hang
    killall -9 uwsgi

    # This cleans up the PID file, but uses pkill so Barbican
    # uWSGI emperor process doesn't actually stop
    stop_process barbican-svc

    stop_process barbican-retry

    stop_process barbican-keystone-listener
}

function get_id {
    echo `"$@" | awk '/ id / { print $4 }'`
}

function create_barbican_accounts {
    #
    # Setup Default Admin User
    #
    SERVICE_PROJECT=$(openstack project list | awk "/ $SERVICE_PROJECT_NAME / { print \$2 }")
    ADMIN_ROLE=$(openstack role list | awk "/ admin / { print \$2 }")

    create_service_user barbican $ADMIN_ROLE
    #
    # Setup Default service-admin User
    #
    SERVICE_ADMIN=$(get_or_create_user \
                        "service-admin" \
                        "$SERVICE_PASSWORD" \
                        "default" \
                        "service-admin@example.com")
    SERVICE_ADMIN_ROLE=$(get_or_create_role "key-manager:service-admin")
    get_or_add_user_project_role \
        "$SERVICE_ADMIN_ROLE" \
        "$SERVICE_ADMIN" \
        "$SERVICE_PROJECT"
    #
    # Setup RBAC User Projects and Roles
    #
    PASSWORD="barbican"
    PROJECT_A_ID=$(get_or_create_project "project_a" "default")
    PROJECT_B_ID=$(get_or_create_project "project_b" "default")
    ROLE_ADMIN_ID=$(get_or_create_role "admin")
    ROLE_CREATOR_ID=$(get_or_create_role "creator")
    ROLE_OBSERVER_ID=$(get_or_create_role "observer")
    ROLE_AUDIT_ID=$(get_or_create_role "audit")
    #
    # Setup RBAC Admin of Project A
    #
    USER_ID=$(get_or_create_user \
                  "project_a_admin" \
                  "$PASSWORD" \
                  "default" \
                  "admin_a@example.net")
    get_or_add_user_project_role "$ROLE_ADMIN_ID" "$USER_ID" "$PROJECT_A_ID"
    #
    # Setup RBAC Creator of Project A
    #
    USER_ID=$(get_or_create_user \
                  "project_a_creator" \
                  "$PASSWORD" \
                  "default" \
                  "creator_a@example.net")
    get_or_add_user_project_role "$ROLE_CREATOR_ID" "$USER_ID" "$PROJECT_A_ID"
    # Adding second creator user in project_a
    USER_ID=$(get_or_create_user \
                  "project_a_creator_2" \
                  "$PASSWORD" \
                  "default" \
                  "creator2_a@example.net")
    get_or_add_user_project_role "$ROLE_CREATOR_ID" "$USER_ID" "$PROJECT_A_ID"
    #
    # Setup RBAC Observer of Project A
    #
    USER_ID=$(get_or_create_user \
                  "project_a_observer" \
                  "$PASSWORD" \
                  "default" \
                  "observer_a@example.net")
    get_or_add_user_project_role "$ROLE_OBSERVER_ID" "$USER_ID" "$PROJECT_A_ID"
    #
    # Setup RBAC Auditor of Project A
    #
    USER_ID=$(get_or_create_user \
                  "project_a_auditor" \
                  "$PASSWORD" \
                  "default" \
                  "auditor_a@example.net")
    get_or_add_user_project_role "$ROLE_AUDIT_ID" "$USER_ID" "$PROJECT_A_ID"
    #
    # Setup RBAC Admin of Project B
    #
    USER_ID=$(get_or_create_user \
                  "project_b_admin" \
                  "$PASSWORD" \
                  "default" \
                  "admin_b@example.net")
    get_or_add_user_project_role "$ROLE_ADMIN_ID" "$USER_ID" "$PROJECT_B_ID"
    #
    # Setup RBAC Creator of Project B
    #
    USER_ID=$(get_or_create_user \
                  "project_b_creator" \
                  "$PASSWORD" \
                  "default" \
                  "creator_b@example.net")
    get_or_add_user_project_role "$ROLE_CREATOR_ID" "$USER_ID" "$PROJECT_B_ID"
    #
    # Setup RBAC Observer of Project B
    #
    USER_ID=$(get_or_create_user \
                  "project_b_observer" \
                  "$PASSWORD" \
                  "default" \
                  "observer_b@example.net")
    get_or_add_user_project_role "$ROLE_OBSERVER_ID" "$USER_ID" "$PROJECT_B_ID"
    #
    # Setup RBAC auditor of Project B
    #
    USER_ID=$(get_or_create_user \
                  "project_b_auditor" \
                  "$PASSWORD" \
                  "default" \
                  "auditor_b@example.net")
    get_or_add_user_project_role "$ROLE_AUDIT_ID" "$USER_ID" "$PROJECT_B_ID"
    #
    # Setup Barbican Endpoint
    #
    BARBICAN_SERVICE=$(get_or_create_service \
        "barbican" \
        "key-manager" \
        "Barbican Service")
    # This creates all 3 endpoints (public, admin, internal)
    get_or_create_endpoint \
        "$BARBICAN_SERVICE" \
        "RegionOne" \
        "$SERVICE_PROTOCOL://$SERVICE_HOST/key-manager" \
        "$SERVICE_PROTOCOL://$SERVICE_HOST/key-manager" \
        "$SERVICE_PROTOCOL://$SERVICE_HOST/key-manager"

}

# PyKMIP functions
# ----------------

# install_pykmip - install the PyKMIP python module
# create keys and certificate for server
function install_pykmip {
    pip_install 'pykmip'

    if is_service_enabled pykmip-server; then
        [ ! -d ${PYKMIP_CONF_DIR} ] && sudo mkdir -p ${PYKMIP_CONF_DIR}
        sudo chown ${USER} ${PYKMIP_CONF_DIR}

        [ ! -d ${PYKMIP_LOG_DIR} ] && sudo mkdir -p ${PYKMIP_LOG_DIR}
        sudo chown ${USER} ${PYKMIP_LOG_DIR}

        init_CA
        if [ ! -e ${PYKMIP_SERVER_KEY} ]; then
            make_cert ${INT_CA_DIR} 'pykmip-server' 'pykmip-server'
            chmod 400 ${PYKMIP_SERVER_KEY}
        fi
        if [ ! -e ${PYKMIP_CLIENT_KEY} ]; then
            make_cert ${INT_CA_DIR} 'pykmip-client' 'pykmip-client'
            chmod 400 ${PYKMIP_CLIENT_KEY}
        fi

        if [ ! -e ${PYKMIP_CONF} ]; then
            cat > ${PYKMIP_CONF} <<EOF
[server]
hostname=127.0.0.1
port=5696
certificate_path=${PYKMIP_SERVER_CERT}
key_path=${PYKMIP_SERVER_KEY}
ca_path=${PYKMIP_CA_PATH}
auth_suite=TLS1.2
EOF
       fi
   fi
}

# configure_pykmip - enable KMIP plugin and configure
function configure_pykmip {
    iniset $BARBICAN_CONF secretstore enabled_secretstore_plugins kmip_plugin
    iniset $BARBICAN_CONF kmip_plugin username demo
    iniset $BARBICAN_CONF kmip_plugin password secretpassword
    iniset $BARBICAN_CONF kmip_plugin keyfile ${PYKMIP_CLIENT_KEY}
    iniset $BARBICAN_CONF kmip_plugin certfile ${PYKMIP_CLIENT_CERT}
    iniset $BARBICAN_CONF kmip_plugin ca_certs ${PYKMIP_CA_PATH}
}

# start_pykmip - start the PyKMIP server
function start_pykmip {
    run_process pykmip-server "$BARBICAN_BIN_DIR/pykmip-server -f ${PYKMIP_CONF} -l ${PYKMIP_LOG_DIR}/pykmip-devstack.log"
}

# Dogtag functions
# ----------------

function install_389_directory_server {
    # Make sure that 127.0.0.1 resolves to localhost.localdomain (fqdn)
    sudo sed -i 's/127.0.0.1[ \t]*localhost localhost.localdomain/127.0.0.1\tlocalhost.localdomain localhost/' /etc/hosts

    sudo mkdir -p /etc/389-ds

    dscreate create-template ds.tmp
    sed -e 's/;root_password = .*/root_password = PASSWORD/g' \
	-e 's/;full_machine_name = .*/full_machine_name = localhost.localdomain/g' \
	-e 's/;instance_name =.*/instance_name = pki-tomcat/g' \
	ds.tmp > ds.inf
    rm ds.tmp

    sudo mv ds.inf /etc/389-ds/ds.inf
    sudo dscreate from-file /etc/389-ds/ds.inf
}

function install_dogtag_ca {
    sudo mkdir -p /etc/dogtag

    cat > .tmp.ca.cfg <<EOF
[CA]
pki_admin_email=caadmin@example.com
pki_admin_name=caadmin
pki_admin_nickname=caadmin
pki_admin_password=PASSWORD
pki_admin_uid=caadmin
pki_backup_password=PASSWORD
pki_client_database_password=PASSWORD
pki_client_database_purge=False
pki_client_pkcs12_password=PASSWORD
pki_clone_pkcs12_password=PASSWORD
pki_ds_base_dn=dc=ca,dc=example,dc=com
pki_ds_database=ca
pki_ds_password=PASSWORD
pki_hostname=localhost
pki_security_domain_name=EXAMPLE
pki_token_password=PASSWORD
pki_https_port=8373
pki_http_port=8370
pki_ajp_port=8379
pki_tomcat_server_port=8375
EOF

    sudo mv .tmp.ca.cfg /etc/dogtag/ca.cfg

    sudo pkispawn -v -f /etc/dogtag/ca.cfg -s CA
}

function wait_for_ca {
    while true; do
        # If the sleep command is executed "as-is", the subprocess that it
        # executes will trigger the "exit_trap" and will cause this script to
        # fail. To avoid this, we run the sleep command inside this sub-shell,
        # so the signal will not be caught in this process.
        ca_running=$(sleep 2 && curl -s -k https://localhost:8373/ca/admin/ca/getStatus | grep -c running)
        if [[ $ca_running == 1 ]]; then
            break
        fi
    done
}

function install_dogtag_kra {
    sudo mkdir -p /etc/dogtag

    # Even though we are using localhost.localdomain, the server certificate by
    # default will get the real host name for the server. So we need to
    # properly configure the KRA to try to communicate with the real host name
    # instead of the localhost.
    cat > .tmp.kra.cfg <<EOF
[KRA]
pki_admin_cert_file=/root/.dogtag/pki-tomcat/ca_admin.cert
pki_admin_email=kraadmin@example.com
pki_admin_name=kraadmin
pki_admin_nickname=kraadmin
pki_admin_password=PASSWORD
pki_admin_uid=kraadmin
pki_backup_password=PASSWORD
pki_client_database_password=PASSWORD
pki_client_database_purge=False
pki_client_pkcs12_password=PASSWORD
pki_clone_pkcs12_password=PASSWORD
pki_ds_base_dn=dc=kra,dc=example,dc=com
pki_ds_database=kra
pki_ds_password=PASSWORD
pki_hostname=localhost
pki_security_domain_name=EXAMPLE
pki_security_domain_user=caadmin
pki_security_domain_password=PASSWORD
pki_token_password=PASSWORD
pki_https_port=8373
pki_http_port=8370
pki_ajp_port=8379
pki_tomcat_server_port=8375
pki_security_domain_hostname=localhost
pki_security_domain_https_port=8373
EOF

    sudo mv .tmp.kra.cfg /etc/dogtag/kra.cfg

    sudo pkispawn -v -f /etc/dogtag/kra.cfg -s KRA
}

function install_dogtag_plugin_dependencies {
    install_package nss-devel 389-ds-base dogtag-pki
}

function install_dogtag_components {
    install_dogtag_plugin_dependencies
    install_389_directory_server
    install_dogtag_ca
    wait_for_ca
    install_dogtag_kra
}


# Vault functions
# ----------------

function install_vault {
    # Install vault if needed
    if [[ ! -x "$(command -v vault)" ]]; then
        wget https://releases.hashicorp.com/vault/1.3.0/vault_1.3.0_linux_amd64.zip
        unzip vault_1.3.0_linux_amd64.zip
        sudo mv vault /usr/bin
    fi

    install_package screen
    TOKEN_ID_FILE="${BARBICAN_DIR}/vault_root_token_id"
    local session_name="barbican_vault"

    # Clean up first before starting new screen session
    existing_ses=$(screen -ls | grep ${session_name} | awk '{print $1}')
    if [[ -n "${existing_ses}" ]]; then
        screen -S ${existing_ses} -X quit
    fi
    rm -f ${TOKEN_ID_FILE} vault.log

    screen -dmS ${session_name}
    screen -S ${session_name} -p bash -X stuff 'vault server -dev 2>&1 >vault.log\n'

    # get the root_token_id, use tempfile for counter
    touch $TOKEN_ID_FILE
    COUNTER=0

    while [ ! -s $TOKEN_ID_FILE ] && [ "$COUNTER" -lt "20" ]
    do
        sleep 2
        awk '/Root Token:/ {print $3}' vault.log > $TOKEN_ID_FILE
        COUNTER=$[COUNTER + 1]
    done

    if [ ! -s $TOKEN_ID_FILE ]; then
        echo "Wah! Need to throw an error code here!"
    fi

    export VAULT_ADDR="http://127.0.0.1:8200"

    # Enable kv version 1
    vault secrets disable secret/
    vault secrets enable -version=1 -path=secret -description "kv version 1" kv

    #debug code follows:
    vault status
    vault kv put secret/hello foo=world
    vault kv get secret/hello
    vault kv delete secret/hello

}

function configure_vault_plugin {
    root_token_id=`cat ${BARBICAN_DIR}/vault_root_token_id`
    iniset $BARBICAN_CONF secretstore enabled_secretstore_plugins vault_plugin
    iniset $BARBICAN_CONF vault_plugin root_token_id $root_token_id
    iniset $BARBICAN_CONF vault_plugin vault_url "http://127.0.0.1:8200"
    iniset $BARBICAN_CONF vault_plugin use_ssl "false"
}

# Restore xtrace
$XTRACE
