Chapter 6. The configure-federation file
#!/bin/sh prog_name=`basename $0` action= dry_run=0 verbose=0 base_dir=$(pwd) stage_dir="${base_dir}/fed_deployment" mellon_root="/v3" mellon_endpoint="mellon" mellon_app_name="v3" overcloud_deploy_script="overcloud_deploy.sh" overcloudrc_file="./overcloudrc" function cmd_template { local status=0 local cmd="$1" if [ $verbose -ne 0 -o $dry_run -ne 0 ]; then echo $cmd fi if [ $dry_run -ne 0 ]; then return $status fi $cmd status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR cmd \"$cmd\" failed\nstatus = $status") fi return $status } function cmds_template { local return_status=0 declare -a cmds=( "date" "ls xxx" "head $0" ) if [ $dry_run -ne 0 ]; then for cmd in "${cmds[@]}"; do echo $cmd done else for cmd in "${cmds[@]}"; do if [ $verbose -ne 0 ]; then echo $cmd fi $cmd status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR cmd \"$cmd\" failed\nstatus = $status") return_status=$status fi done fi return $return_status } function show_variables { echo "base_dir: $base_dir" echo "stage_dir: $stage_dir" echo "config_tar_filename: $config_tar_filename" echo "config_tar_pathname: $config_tar_pathname" echo "overcloud_deploy_script: $overcloud_deploy_script" echo "overcloudrc_file: $overcloudrc_file" echo "puppet_override_apache_pathname: $puppet_override_apache_pathname" echo "puppet_override_keystone_pathname: $puppet_override_keystone_pathname" echo echo "FED_RHSSO_URL: $FED_RHSSO_URL" echo "FED_RHSSO_ADMIN_PASSWORD: $FED_RHSSO_ADMIN_PASSWORD" echo "FED_RHSSO_REALM: $FED_RHSSO_REALM" echo echo "FED_KEYSTONE_HOST: $FED_KEYSTONE_HOST" echo "FED_KEYSTONE_HTTPS_PORT: $FED_KEYSTONE_HTTPS_PORT" echo "mellon_http_url: $mellon_http_url" echo "mellon_root: $mellon_root" echo "mellon_endpoint: $mellon_endpoint" echo "mellon_app_name: $mellon_app_name" echo "mellon_endpoint_path: $mellon_endpoint_path" echo "mellon_entity_id: $mellon_entity_id" echo echo "FED_OPENSTACK_IDP_NAME: $FED_OPENSTACK_IDP_NAME" echo "openstack_mapping_pathname: $openstack_mapping_pathname" echo "FED_OPENSTACK_MAPPING_NAME: $FED_OPENSTACK_MAPPING_NAME" echo echo "idp_metadata_filename: $idp_metadata_filename" echo "mellon_httpd_config_filename: $mellon_httpd_config_filename" } function initialize { local return_status=0 declare -a cmds=( "mkdir -p $stage_dir" ) if [ $dry_run -ne 0 ]; then for cmd in "${cmds[@]}"; do echo $cmd done else for cmd in "${cmds[@]}"; do if [ $verbose -ne 0 ]; then echo $cmd fi $cmd status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR cmd \"$cmd\" failed\nstatus = $status") return_status=$status fi done fi return $return_status } function copy_helper_to_controller { local status=0 local controller=${1:-"controller-0"} local cmd="scp configure-federation fed_variables heat-admin@${controller}:/home/heat-admin" if [ $verbose -ne 0 -o $dry_run -ne 0 ]; then echo $cmd fi if [ $dry_run -ne 0 ]; then return $status fi $cmd status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR cmd \"$cmd\" failed\nstatus = $status") fi return $status } function install_mod_auth_mellon { local status=0 local cmd="sudo dnf -y install mod_auth_mellon" if [ $verbose -ne 0 -o $dry_run -ne 0 ]; then echo $cmd fi if [ $dry_run -ne 0 ]; then return $status fi $cmd status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR cmd \"$cmd\" failed\nstatus = $status") fi return $status } function create_ipa_service_account { # Note, after setting up the service account it can be tested # by performing a user search like this: # ldapsearch -H $ldap_url -x -D "$service_dn" -w "$FED_IPA_RHSSO_SERVICE_PASSWD" -b "cn=users,cn=accounts,$FED_IPA_BASE_DN" local status=0 local ldap_url="ldaps://$FED_IPA_HOST" local dir_mgr_dn="cn=Directory Manager" local service_name="rhsso" local service_dn="uid=$service_name,cn=sysaccounts,cn=etc,$FED_IPA_BASE_DN" local cmd="ldapmodify -H \"$ldap_url\" -x -D \"$dir_mgr_dn\" -w \"$FED_IPA_ADMIN_PASSWD\"" read -r -d '' contents <<EOF dn: $service_dn changetype: add objectclass: account objectclass: simplesecurityobject uid: $service_name userPassword: $FED_IPA_RHSSO_SERVICE_PASSWD passwordExpirationTime: 20380119031407Z nsIdleTimeout: 0 EOF if [ $verbose -ne 0 -o $dry_run -ne 0 ]; then echo $cmd echo -e "$contents" fi if [ $dry_run -ne 0 ]; then return $status fi sh <<< "$cmd <<< \"$contents\"" status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR cmd \"$cmd\" failed\nstatus = $status") fi return $status } function client_install { local status=0 local cmd_client_install="sudo dnf -y install keycloak-httpd-client-install" local cmd="sudo keycloak-httpd-client-install \ --client-originate-method registration \ --mellon-https-port $FED_KEYSTONE_HTTPS_PORT \ --mellon-hostname $FED_KEYSTONE_HOST \ --mellon-root $mellon_root \ --keycloak-server-url $FED_RHSSO_URL \ --keycloak-admin-password $FED_RHSSO_ADMIN_PASSWORD \ --app-name $mellon_app_name \ --keycloak-realm $FED_RHSSO_REALM \ -l "/v3/auth/OS-FEDERATION/websso/mapped" \ -l "/v3/auth/OS-FEDERATION/identity_providers/rhsso/protocols/mapped/websso" \ -l "/v3/OS-FEDERATION/identity_providers/rhsso/protocols/mapped/auth" " if [ $verbose -ne 0 -o $dry_run -ne 0 ]; then echo $cmd_client_install echo $cmd fi if [ $dry_run -ne 0 ]; then return $status fi $cmd_client_install status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR cmd \"$cmd_client_install\" failed\nstatus = $status") else $cmd status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR cmd \"$cmd\" failed\nstatus = $status") fi fi return $status } function create_sp_archive { # Note, we put the exclude patterns in a file because it is # insanely difficult to put --exclude patttern in the $cmd shell # variable and get the final quoting correct. local status=0 local cmd="tar -cvzf $config_tar_pathname --exclude-from $stage_dir/tar_excludes /var/lib/config-data/puppet-generated/keystone/etc/httpd/federation /var/lib/config-data/puppet-generated/keystone/etc/httpd/conf.d/$mellon_httpd_config_filename" if [ $verbose -ne 0 -o $dry_run -ne 0 ]; then echo $cmd fi if [ $dry_run -ne 0 ]; then return $status fi cat <<'EOF' > $stage_dir/tar_excludes *.orig *~ EOF $cmd status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR cmd \"$cmd\" failed\nstatus = $status") fi return $status } function fetch_sp_archive { local return_status=0 declare -a cmds=( "scp heat-admin@controller-0:/home/heat-admin/fed_deployment/$config_tar_filename $stage_dir" "tar -C $stage_dir -xvf $config_tar_pathname" ) if [ $dry_run -ne 0 ]; then for cmd in "${cmds[@]}"; do echo $cmd done else for cmd in "${cmds[@]}"; do if [ $verbose -ne 0 ]; then echo $cmd fi $cmd status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR cmd \"$cmd\" failed\nstatus = $status") return_status=$status fi done fi return $return_status } function deploy_mellon_configuration { local status=0 local cmd="upload-swift-artifacts -f $config_tar_pathname" if [ $verbose -ne 0 -o $dry_run -ne 0 ]; then echo $cmd fi if [ $dry_run -ne 0 ]; then return $status fi $cmd status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR cmd \"$cmd\" failed\nstatus = $status") fi return $status } function idp_entity_id { local metadata_file=${1:-$idp_metadata_filename} # Extract the entitID from the metadata file, should really be parsed # with an XML xpath but a simple string match is probably OK entity_id=`sed -rne 's/^.*entityID="([^"]*)".*$/\1/p' ${metadata_file}` status=$? if [ $status -ne 0 -o "$entity_id"x = "x" ]; then (>&2 echo -e "ERROR search for entityID in ${metadata_file} failed\nstatus = $status") return 1 fi echo $entity_id return 0 } function append_deploy_script { local status=0 local deploy_script=$1 local extra_line=$2 local count count=$(grep -c -e "$extra_line" $deploy_script) if [ $count -eq 1 ]; then echo -e "SKIP appending:\n$extra_line" echo "already present in $deploy_script" return $status elif [ $count -gt 1 ]; then status=1 (>&2 echo -e "ERROR multiple copies of line in ${deploy_script}\nstatus = $status\nline=$extra_line") return $status fi if [ $verbose -ne 0 -o $dry_run -ne 0 ]; then echo "appending $deploy_script with:" echo -e $extra_line fi if [ $dry_run -ne 0 ]; then return $status fi # insert line after last -e line already in script # # This is not easy with sed, we'll use tac and awk instead. Here # is how this works: The logic is easier if you insert before the # first line rather than trying to find the last line and insert # after it. We use tac to reverse the lines in the file. Then the # awk script looks for the candidate line. If found it outputs the # line we're adding, sets a flag (p) to indicate it's already been # printed. The "; 1" pattern always output the input line. Then we # run the output through tac again to set things back in the # original order. local tmp_file=$(mktemp) tac $deploy_script | awk "!p && /^-e/{print \"${extra_line} \\\\\"; p=1}; 1" | tac > $tmp_file count=$(grep -c -e "${extra_line}" $tmp_file) if [ $count -ne 1 ]; then status=1 fi if [ $status -ne 0 ]; then rm $tmp_file (>&2 echo -e "ERROR failed to append ${deploy_script}\nstatus = $status\nline=$extra_line") else mv $tmp_file $deploy_script fi return $status } function puppet_override_apache { local status=0 local pathname=${1:-$puppet_override_apache_pathname} local deploy_cmd="-e $pathname" read -r -d '' contents <<'EOF' parameter_defaults: ControllerExtraConfig: apache::purge_configs: false EOF if [ $verbose -ne 0 -o $dry_run -ne 0 ]; then echo "writing pathname = $pathname with contents" echo -e "$contents" fi if [ $dry_run -ne 0 ]; then return $status fi echo -e "$contents" > $pathname status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR failed to write ${pathname}\nstatus = $status") fi append_deploy_script $overcloud_deploy_script "$deploy_cmd" status=$? return $status } function puppet_override_keystone { local status=0 local pathname=${1:-$puppet_override_keystone_pathname} local deploy_cmd="-e $pathname" read -r -d '' contents <<EOF parameter_defaults: controllerExtraConfig: keystone::using_domain_config: true keystone::config::keystone_config: identity/domain_configurations_from_database: value: true auth/methods: value: external,password,token,oauth1,mapped federation/trusted_dashboard: value: https://$FED_KEYSTONE_HOST/dashboard/auth/websso/ federation/sso_callback_template: value: /etc/keystone/sso_callback_template.html federation/remote_id_attribute: value: MELLON_IDP EOF if [ $verbose -ne 0 -o $dry_run -ne 0 ]; then echo "writing pathname = $pathname with contents" echo -e "$contents" fi if [ $dry_run -ne 0 ]; then return $status fi echo -e "$contents" > $pathname status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR failed to write ${pathname}\nstatus = $status") fi append_deploy_script $overcloud_deploy_script "$deploy_cmd" status=$? return $status } function create_federated_resources { # follow example in Keystone federation documentation # http://docs.openstack.org/developer/keystone/federation/federated_identity.html#create-keystone-groups-and-assign-roles local return_status=0 declare -a cmds=( "openstack domain create federated_domain" "openstack project create --domain federated_domain federated_project" "openstack group create federated_users --domain federated_domain" "openstack role add --group federated_users --group-domain federated_domain --domain federated_domain _member_" "openstack role add --group federated_users --project federated_project Member" ) if [ $dry_run -ne 0 ]; then for cmd in "${cmds[@]}"; do echo $cmd done else for cmd in "${cmds[@]}"; do if [ $verbose -ne 0 ]; then echo $cmd fi $cmd status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR cmd \"$cmd\" failed\nstatus = $status") return_status=$status fi done fi return $return_status } function create_mapping { # Matches documentation # http://docs.openstack.org/developer/keystone/federation/federated_identity.html#create-keystone-groups-and-assign-roles local status=0 local pathname=${1:-$openstack_mapping_pathname} read -r -d '' contents <<'EOF' [ { "local": [ { "user": { "name": "{0}" }, "group": { "domain": { "name": "federated_domain" }, "name": "federated_users" } } ], "remote": [ { "type": "MELLON_NAME_ID" }, { "type": "MELLON_groups", "any_one_of": ["openstack-users"] } ] } ] EOF if [ $verbose -ne 0 -o $dry_run -ne 0 ]; then echo "writing pathname = $pathname with contents" echo -e "$contents" fi if [ $dry_run -ne 0 ]; then return $status fi echo -e "$contents" > $pathname status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR failed to write ${pathname}\nstatus = $status") fi return $status } function create_v3_rcfile { local status=0 local input_file=${1:-$overcloudrc_file} local output_file="${input_file}.v3" source $input_file #clear the old environment NEW_OS_AUTH_URL=`echo $OS_AUTH_URL | sed 's!v2.0!v3!'` read -r -d '' contents <<EOF for key in \$( set | sed 's!=.*!!g' | grep -E '^OS_') ; do unset $key ; done export OS_AUTH_URL=$NEW_OS_AUTH_URL export OS_USERNAME=$OS_USERNAME export OS_PASSWORD=$OS_PASSWORD export OS_USER_DOMAIN_NAME=Default export OS_PROJECT_DOMAIN_NAME=Default export OS_PROJECT_NAME=$OS_TENANT_NAME export OS_IDENTITY_API_VERSION=3 EOF if [ $verbose -ne 0 -o $dry_run -ne 0 ]; then echo "writing output_file = $output_file with contents:" echo -e "$contents" fi if [ $dry_run -ne 0 ]; then return $status fi echo -e "$contents" > $output_file status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR failed to write ${output_file}\nstatus = $status") fi return $status } function openstack_create_idp { local status=0 local metadata_file="$stage_dir/var/lib/config-data/puppet-generated/keystone/etc/httpd/federation/$idp_metadata_filename" local entity_id entity_id=$(idp_entity_id $metadata_file) status=$? if [ $status -ne 0 ]; then return $status fi local cmd="openstack identity provider create --remote-id $entity_id $FED_OPENSTACK_IDP_NAME" if [ $verbose -ne 0 -o $dry_run -ne 0 ]; then echo $cmd fi if [ $dry_run -ne 0 ]; then return $status fi $cmd status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR cmd \"$cmd\" failed\nstatus = $status") fi return $status } function openstack_create_mapping { local status=0 local mapping_file=${1:-$openstack_mapping_pathname} local mapping_name=${2:-$FED_OPENSTACK_MAPPING_NAME} cmd="openstack mapping create --rules $mapping_file $mapping_name" if [ $verbose -ne 0 -o $dry_run -ne 0 ]; then echo $cmd fi if [ $dry_run -ne 0 ]; then return $status fi $cmd status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR cmd \"$cmd\" failed\nstatus = $status") fi return $status } function openstack_create_protocol { local status=0 local idp_name=${1:-$FED_OPENSTACK_IDP_NAME} local mapping_name=${2:-$FED_OPENSTACK_MAPPING_NAME} cmd="openstack federation protocol create --identity-provider $idp_name --mapping $mapping_name mapped" if [ $verbose -ne 0 -o $dry_run -ne 0 ]; then echo $cmd fi if [ $dry_run -ne 0 ]; then return $status fi $cmd status=$? if [ $status -ne 0 ]; then (>&2 echo -e "ERROR cmd \"$cmd\" failed\nstatus = $status") fi return $status } function usage { cat <<EOF $prog_name action -h --help print usage -n --dry-run dry run, just print computed command -v --verbose be chatty action may be one of: show-variables initialize copy-helper-to-controller install-mod-auth-mellon create-ipa-service-account client-install create-sp-archive fetch-sp-archive deploy-mellon-configuration puppet-override-apache puppet-override-keystone create-federated-resources create-mapping create-v3-rcfile openstack-create-idp openstack-create-mapping openstack-create-protocol EOF } #----------------------------------------------------------------------------- # options may be followed by one colon to indicate they have a required argument if ! options=$(getopt -o hnv -l help,dry-run,verbose -- "$@") then # something went wrong, getopt will put out an error message for us exit 1 fi eval set -- "$options" while [ $# -gt 0 ] do case $1 in -h|--help) usage; exit 1 ;; -n|--dry-run) dry_run=1 ;; -v|--verbose) verbose=1 ;; # for options with required arguments, an additional shift is required (--) shift; break;; (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;; (*) break;; esac shift done #----------------------------------------------------------------------------- source ./fed_variables # Strip leading and trailing space and slash from these variables mellon_root=`echo ${mellon_root} | perl -pe 's!^[ /]*(.*?)[ /]*$!\1!'` mellon_endpoint=`echo ${mellon_endpoint} | perl -pe 's!^[ /]*(.*?)[ /]*$!\1!'` mellon_root="/${mellon_root}" mellon_endpoint_path="${mellon_root}/${mellon_endpoint}" mellon_http_url="https://${FED_KEYSTONE_HOST}:${FED_KEYSTONE_HTTPS_PORT}" mellon_entity_id="${mellon_http_url}${mellon_endpoint_path}/metadata" openstack_mapping_pathname="${stage_dir}/mapping_${FED_OPENSTACK_IDP_NAME}_saml2.json" idp_metadata_filename="${mellon_app_name}_keycloak_${FED_RHSSO_REALM}_idp_metadata.xml" mellon_httpd_config_filename="${mellon_app_name}_mellon_keycloak_${FED_RHSSO_REALM}.conf" config_tar_filename="rhsso_config.tar.gz" config_tar_pathname="${stage_dir}/${config_tar_filename}" puppet_override_apache_pathname="${stage_dir}/puppet_override_apache.yaml" puppet_override_keystone_pathname="${stage_dir}/puppet_override_keystone.yaml" #----------------------------------------------------------------------------- if [ $# -lt 1 ]; then echo "ERROR: no action specified" exit 1 fi action="$1"; shift if [ $dry_run -ne 0 ]; then echo "Dry Run Enabled!" fi case $action in show-var*) show_variables ;; initialize) initialize ;; copy-helper-to-controller) copy_helper_to_controller "$1" ;; install-mod-auth-mellon) install_mod_auth_mellon ;; create-ipa-service-account) create_ipa_service_account ;; client-install) client_install ;; create-sp-archive) create_sp_archive ;; fetch-sp-archive) fetch_sp_archive ;; deploy-mellon-configuration) deploy_mellon_configuration ;; create-v3-rcfile) create_v3_rcfile "$1" ;; puppet-override-apache) puppet_override_apache "$1" ;; puppet-override-keystone) puppet_override_keystone "$1" ;; create-federated-resources) create_federated_resources ;; create-mapping) create_mapping "$1" ;; openstack-create-idp) openstack_create_idp "$1" ;; openstack-create-mapping) openstack_create_mapping "$1" "$2" ;; openstack-create-protocol) openstack_create_protocol "$1" "$2" ;; *) echo "unknown action: $action" usage exit 1 ;; esac