aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md22
-rw-r--r--defaults/main.yml.example2
-rw-r--r--examples/certreq.yml20
-rw-r--r--files/certreq.conf.example8
-rwxr-xr-xfiles/certreq.sh336
-rwxr-xr-xfiles/framework.sh406
-rw-r--r--handlers/main.yml8
-rw-r--r--main.yml19
-rw-r--r--tasks/1_certreq.yml41
-rw-r--r--tasks/2_generate_pfx.yml29
-rw-r--r--tasks/main.yml9
l---------vars/prod1.example.com.yml.example1
-rw-r--r--vars/prod1.yml.example5
13 files changed, 906 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..649cb31
--- /dev/null
+++ b/README.md
@@ -0,0 +1,22 @@
+# README.md
+## Package: ansible role certreq
+
+This role, certreq, is designed to make it easy for a Linux machine to acquire a certificate signed by a Microsoft Subordinate CA
+
+Its main use is inside a playbook that resembles:
+
+ - hosts: all
+ remote_user: ansible_rdu
+ roles:
+ - certreq
+
+Call the playbook with:
+ansible-playbook -i /etc/ansible/patching/rdu/rdu_patch_list /etc/ansible/configuration/test/certreq.yml -l sw*
+
+The role generates a pkc12 file at /tmp/certnew.pfx with the client cert, ca cert chain, and client private key.
+
+# References
+https://bgstack15.wordpress.com/2016/06/30/manipulating-ssl-certificates/
+fundamental curl statements https://stackoverflow.com/questions/31283476/submitting-base64-csr-to-a-microsoft-ca-via-curl/39722983#39722983
+Use template name, not "template display name" https://social.technet.microsoft.com/Forums/en-US/d5cafc77-3376-43ca-94fd-6b07f7cb193f/using-certutilcertreq-to-get-sccm-client-certs-nondomain-clients?forum=configmgrgeneral
+
diff --git a/defaults/main.yml.example b/defaults/main.yml.example
new file mode 100644
index 0000000..d7ac9ec
--- /dev/null
+++ b/defaults/main.yml.example
@@ -0,0 +1,2 @@
+certificate_template: "ConfigMgrLinuxClientCertificate"
+ca_host: 'ca2.ad.example.com'
diff --git a/examples/certreq.yml b/examples/certreq.yml
new file mode 100644
index 0000000..e7af898
--- /dev/null
+++ b/examples/certreq.yml
@@ -0,0 +1,20 @@
+---
+# File: /etc/ansible/configuration/test/certreq.yml
+# Author: bgstack15
+# Startdate: 2017-11-14
+# Title: Playbook that Deploys The Role certreq
+# Purpose: Wrapper for the certreq role
+# History:
+# Usage:
+# ansible-playbook -i /etc/ansible/patching/rdu/rdu_patch_list /etc/ansible/configuration/test/certreq.yml --become -u ansible_rdu -l sw*
+# Use this playbook when you want to assign a new Microsoft CA-signed certificate to a Linux host for SCCM.
+# Reference:
+# ansible reference online
+# Improve:
+# Document: Below this line
+
+- hosts: all
+ roles:
+ - certreq
+
+...
diff --git a/files/certreq.conf.example b/files/certreq.conf.example
new file mode 100644
index 0000000..a956241
--- /dev/null
+++ b/files/certreq.conf.example
@@ -0,0 +1,8 @@
+CERTREQ_USER="ANONYMOUS"
+CERTREQ_PASS="NOPASSWORD"
+CERTREQ_WORKDIR="$( mktemp -d )"
+CERTREQ_TEMPLATE="ConfigMgrLinuxClientCertificate"
+CERTREQ_CNLONG="$( hostname -f )"
+CERTREQ_CNSHORT="$( echo "${CERTREQ_CNLONG%%.*}" )"
+CERTREQ_SUBJECT="/DC=com/DC=example/DC=ad/CN=${CERTREQ_CNSHORT}/CN=${CERTREQ_CNPARAM:-CERTREQ_CNPARAM}"
+CERTREQ_CA="http://ca2.ad.example.com"
diff --git a/files/certreq.sh b/files/certreq.sh
new file mode 100755
index 0000000..dea652f
--- /dev/null
+++ b/files/certreq.sh
@@ -0,0 +1,336 @@
+#!/bin/sh
+# Filename: certreq.sh
+# Location: atower201:/etc/ansible/roles/certreq/files/certreq.sh
+# Author: bgstack15@gmail.com
+# Startdate: 2017-11-17 09:13:53
+# Title: Script that Requests a Certificate from a Microsoft Sub-CA
+# Purpose: Automate host certificate generation in a domain environment
+# Package: ansible role certreq
+# History:
+# 2017-11-22 Added ca cert chain
+# Usage: in ansible role certreq
+# Microsoft CA cert templates have permissions on them. A user must be able to "enroll" on the template.
+# Reference: ftemplate.sh 2017-10-10x; framework.sh 2017-10-09a
+# fundamental curl statements https://stackoverflow.com/questions/31283476/submitting-base64-csr-to-a-microsoft-ca-via-curl/39722983#39722983
+# Improve:
+fiversion="2017-10-10x"
+certreqversion="2017-11-29a"
+
+usage() {
+ less -F >&2 <<ENDUSAGE
+usage: certreq.sh [-dhV] [-u username] [-p password] [-w tempdir] [-t template] [--cn CN] [--ca <CA hostname>]
+version ${certreqversion}
+ -d debug Show debugging info, including parsed variables.
+ -h usage Show this usage block.
+ -V version Show script version number.
+ -u username User to connect via ntlm to CA. Can be "username" or "domain\\username"
+ -p password
+ -w workdir Temp directory to work in. Default is a (mktemp -d).
+ -t template Template to request from CA. Default is "ConfigMgrLinuxClientCertificate"
+ --cn CN to request. Default is the \$( hostname -f )
+ --ca CA hostname or base URL. Example: ca2.example.com
+Return values under 1000: A non-zero value is the sum of the items listed here:
+ 0 Everything worked
+ 1 Cert file is still a CSR
+ 2 Cert file is html, probably due to permissions/credentials issue
+ 4 Return code of curl statement that saves cert file is non-zero
+ 8 Cert file does not contain whole certificate
+16 Cert does not contain an issuer
+Return values above 1000:
+1001 Help or version info displayed
+1002 Count or type of flaglessvals is incorrect
+1003 Incorrect OS type
+1004 Unable to find dependency
+1005 Not run as root or sudo
+ENDUSAGE
+}
+
+# DEFINE FUNCTIONS
+
+# DEFINE TRAPS
+
+clean_certreq() {
+ # use at end of entire script if you need to clean up tmpfiles
+ #rm -f ${tmpfile} 1>/dev/null 2>&1
+ if test -z "${CR_NC}";
+ then
+ nohup /bin/bash <<EOF 1>/dev/null 2>&1 &
+sleep "${CERTREQ_CLEANUP_SEC:-300}" ; /bin/rm -r "${CERTREQ_WORKDIR}" 2>/dev/null ;
+EOF
+#sleep "${CERTREQ_CLEANUP_SEC:-300}" ; /bin/rm -r "${CERTREQ_WORKDIR}" 2>/dev/null ; echo "slash-dollar-0=\"\$0\" slash-slash-dollar-0=\"\\$0\"" > /dev/pts/2 ;
+ fi
+
+}
+
+CTRLC() {
+ # use with: trap "CTRLC" 2
+ # useful for controlling the ctrl+c keystroke
+ :
+}
+
+CTRLZ() {
+ # use with: trap "CTRLZ" 18
+ # useful for controlling the ctrl+z keystroke
+ :
+}
+
+parseFlag() {
+ flag="$1"
+ hasval=0
+ case ${flag} in
+ # INSERT FLAGS HERE
+ "d" | "debug" | "DEBUG" | "dd" ) setdebug; ferror "debug level ${debug}";;
+ "usage" | "help" | "h" ) usage; exit 1001;;
+ "V" | "fcheck" | "version" ) ferror "${scriptfile} version ${certreqversion}"; exit 1001;;
+ "u" | "user" | "username" ) getval; CERTREQ_USER="${tempval}";;
+ "p" | "pass" | "password" ) getval; CERTREQ_PASS="${tempval}";;
+ "w" | "work" | "workdir" ) getval; CERTREQ_WORKDIR="${tempval}";;
+ "t" | "temp" | "template" ) getval; CERTREQ_TEMPLATE="${tempval}";;
+ "cn" | "common-name" | "commonname" ) getval; CERTREQ_CNPARAM="${tempval}";;
+ "ca" | "certauthority" | "cauthority" ) getval; CERTREQ_CAPARAM="${tempval}";;
+ "c" | "conf" | "conffile" | "config" ) getval; conffile="${tempval}";;
+ "nc" | "nocleanup" ) CR_NC=1;;
+ esac
+
+ debuglev 10 && { test ${hasval} -eq 1 && ferror "flag: ${flag} = ${tempval}" || ferror "flag: ${flag}"; }
+}
+
+# DETERMINE LOCATION OF FRAMEWORK
+while read flocation; do if test -f ${flocation} && test "$( sh ${flocation} --fcheck )" -ge 20170608; then frameworkscript="${flocation}"; break; fi; done <<EOFLOCATIONS
+./framework.sh
+${scriptdir}/framework.sh
+/tmp/framework.sh
+/usr/share/bgscripts/framework.sh
+EOFLOCATIONS
+test -z "${frameworkscript}" && echo "$0: framework not found. Aborted." 1>&2 && exit 1004
+
+# INITIALIZE VARIABLES
+# variables set in framework:
+# today server thistty scriptdir scriptfile scripttrim
+# is_cronjob stdin_piped stdout_piped stderr_piped sendsh sendopts
+. ${frameworkscript} || echo "$0: framework did not run properly. Continuing..." 1>&2
+infile1=
+outfile1=
+#logfile=${scriptdir}/${scripttrim}.${today}.out # defined farther down
+define_if_new interestedparties "bgstack15@gmail.com"
+# SIMPLECONF
+define_if_new default_conffile "/tmp/certreq.conf"
+define_if_new defuser_conffile ~/.config/certreq/certreq.conf
+
+# REACT TO OPERATING SYSTEM TYPE
+case $( uname -s ) in
+ Linux) [ ];;
+ *) echo "${scriptfile}: 3. Indeterminate OS: $( uname -s )" 1>&2 && exit 1003;;
+esac
+
+## REACT TO ROOT STATUS
+#case ${is_root} in
+# 1) # proper root
+# [ ] ;;
+# sudo) # sudo to root
+# [ ] ;;
+# "") # not root at all
+# #ferror "${scriptfile}: 5. Please run as root or sudo. Aborted."
+# #exit 1005
+# [ ]
+# ;;
+#esac
+
+# SET CUSTOM SCRIPT AND VALUES
+#setval 1 sendsh sendopts<<EOFSENDSH # if $1="1" then setvalout="critical-fail" on failure
+#/usr/share/bgscripts/send.sh -hs # setvalout maybe be "fail" otherwise
+#/usr/local/bin/send.sh -hs # on success, setvalout="valid-sendsh"
+#/usr/bin/mail -s
+#EOFSENDSH
+#test "${setvalout}" = "critical-fail" && ferror "${scriptfile}: 4. mailer not found. Aborted." && exit 1004
+
+# VALIDATE PARAMETERS
+# objects before the dash are options, which get filled with the optvals
+# to debug flags, use option DEBUG. Variables set in framework: fallopts
+validateparams - "$@"
+
+# CONFIRM TOTAL NUMBER OF FLAGLESSVALS IS CORRECT
+#if test ${thiscount} -lt 2;
+#then
+# ferror "${scriptfile}: 2. Fewer than 2 flaglessvals. Aborted."
+# exit 1002
+#fi
+
+# LOAD CONFIG FROM SIMPLECONF
+# This section follows a simple hierarchy of precedence, with first being used:
+# 1. parameters and flags
+# 2. environment
+# 3. config file
+# 4. default user config: ~/.config/script/script.conf
+# 5. default config: /etc/script/script.conf
+if test -f "${conffile}";
+then
+ get_conf "${conffile}"
+else
+ if test "${conffile}" = "${default_conffile}" || test "${conffile}" = "${defuser_conffile}"; then :; else test -n "${conffile}" && ferror "${scriptfile}: Ignoring conf file which is not found: ${conffile}."; fi
+fi
+test -f "${defuser_conffile}" && get_conf "${defuser_conffile}"
+test -f "${default_conffile}" && get_conf "${default_conffile}"
+
+# CONFIGURE VARIABLES AFTER PARAMETERS
+define_if_new CERTREQ_USER "ANONYMOUS"
+define_if_new CERTREQ_PASS "NOPASSWORD"
+test -z "${CERTREQ_WORKDIR}" && CERTREQ_WORKDIR="$( mktemp -d )"
+define_if_new CERTREQ_TEMPLATE "ConfigMgrLinuxClientCertificate"
+define_if_new CERTREQ_CNLONG "$( hostname -f )"
+define_if_new CERTREQ_CNSHORT "$( echo "${CERTREQ_CNLONG%%.*}" )"
+define_if_new CERTREQ_CLEANUP_SEC 300
+logfile="$( TMPDIR="${CERTREQ_WORKDIR}" mktemp -t tmp.XXXXXXXXXX )"
+CERTREQ_TEMPFILE="$( TMPDIR="${CERTREQ_WORKDIR}" mktemp -t tmp.XXXXXXXXXX )"
+
+# calculate the subject
+if test -n "${CERTREQ_CNPARAM}";
+then
+ # ensure good CN format.
+ CERTREQ_CNPARAM="$( echo "${CERTREQ_CNPARAM}" | sed -r -e 's/^CN=//i;' )"
+ case "${CERTREQ_CNPARAM}" in
+ "${CERTREQ_CNLONG}" | "${CERTREQ_CNSHORT}" ) : ;;
+ *) ferror "Using custom CN \"${CERTREQ_CNPARAM}\"" ;;
+ esac
+else
+ CERTREQ_CNPARAM="${CERTREQ_CNLONG}"
+fi
+CERTREQ_SUBJECT="$( echo ${CERTREQ_SUBJECT} | sed -r -e "s/CERTREQ_CNPARAM/${CERTREQ_CNPARAM}/g;" )"
+define_if_new CERTREQ_SUBJECT "/DC=com/DC=example/DC=ad/CN=${CERTREQ_CNSHORT}/CN=${CERTREQ_CNPARAM}"
+
+# calculate the MSCA
+if test -n "${CERTREQ_CAPARAM}";
+then
+ # trim down to just the hostname
+ CERTREQ_CAPARAM="$( echo "${CERTREQ_CAPARAM}" | sed -r -e 's/https?:\/\///g' -e 's/(\.[a-z]{2,3})\/$/\1/;' )"
+ CERTREQ_CA="http://${CERTREQ_CAPARAM}"
+fi
+define_if_new CERTREQ_CA "http://ca2.ad.example.com"
+# generate cahost
+CERTREQ_CAHOST="$( echo "${CERTREQ_CA}" | sed -r -e 's/https?:\/\///g' -e 's/(\.[a-z]{2,3})\/$/\1/;' )"
+
+## REACT TO BEING A CRONJOB
+#if test ${is_cronjob} -eq 1;
+#then
+# [ ]
+#else
+# [ ]
+#fi
+
+# SET TRAPS
+#trap "CTRLC" 2
+#trap "CTRLZ" 18
+trap "clean_certreq" 0
+
+# DEBUG SIMPLECONF
+debuglev 5 && {
+ ferror "Using values"
+ # used values: EX_(OPT1|OPT2|VERBOSE)
+ set | grep -iE "^CERTREQ_" | {
+ if fistruthy "${NO_MASK}" ;
+ then
+ cat
+ else
+ sed -r -e 's/(CERTREQ_PASS=).*$/\1**********************/;'
+ fi
+ } 1>&2
+}
+
+# MAIN LOOP
+{
+ # GENERATE PRIVATE KEY
+ openssl req -new -nodes \
+ -out "${CERTREQ_WORKDIR}/${CERTREQ_CNPARAM}.crt" \
+ -keyout "${CERTREQ_WORKDIR}/${CERTREQ_CNPARAM}.key" \
+ -subj "${CERTREQ_SUBJECT}"
+ CERT="$( cat "${CERTREQ_WORKDIR}/${CERTREQ_CNPARAM}.crt" | tr -d '\n\r' )"
+ DATA="Mode=newreq&CertRequest=${CERT}&C&TargetStoreFlags=0&SaveCert=yes"
+ CERT="$( echo ${CERT} | sed -e 's/+/%2B/g' | tr -s ' ' '+' )"
+ CERTATTRIB="CertificateTemplate:${CERTREQ_TEMPLATE}"
+
+ # SUBMIT CERTIFICATE SIGNING REQUEST
+ OUTPUTLINK="$( curl -k -u "${CERTREQ_USER}:${CERTREQ_PASS}" --ntlm \
+ "${CERTREQ_CA}/certsrv/certfnsh.asp" \
+ -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
+ -H 'Accept-Encoding: gzip, deflate' \
+ -H 'Accept-Language: en-US,en;q=0.5' \
+ -H 'Connection: keep-alive' \
+ -H "Host: ${CERTREQ_CAHOST}" \
+ -H "Referer: ${CERTREQ_CA}/certsrv/certrqxt.asp" \
+ -H 'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko' \
+ -H 'Content-Type: application/x-www-form-urlencoded' \
+ --data "Mode=newreq&CertRequest=${CERT}&CertAttrib=${CERTATTRIB}&TargetStoreFlags=0&SaveCert=yes&ThumbPrint=" | grep -A 1 'function handleGetCert() {' | tail -n 1 | cut -d '"' -f 2 )"
+ CERTLINK="${CERTREQ_CA}/certsrv/${OUTPUTLINK}"
+
+ # FETCH SIGNED CERTIFICATE
+ curl -k -u "${CERTREQ_USER}:${CERTREQ_PASS}" --ntlm $CERTLINK \
+ -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
+ -H 'Accept-Encoding: gzip, deflate' \
+ -H 'Accept-Language: en-US,en;q=0.5' \
+ -H 'Connection: keep-alive' \
+ -H "Host: ${CERTREQ_CAHOST}" \
+ -H "Referer: ${CERTREQ_CA}/certsrv/certrqxt.asp" \
+ -H 'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko' \
+ -H 'Content-Type: application/x-www-form-urlencoded' > "${CERTREQ_WORKDIR}/${CERTREQ_CNPARAM}.crt"
+ finaloutput=$?
+
+ # GET NUMBER OF CURRENT CA CERT
+ RESPONSE="$( curl -s -k -u "${CERTREQ_USER}:${CERTREQ_PASS}" --ntlm \
+ "${CERTREQ_CA}/certsrv/certcarc.asp" \
+ -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
+ -H 'Accept-Encoding: gzip, deflate' \
+ -H 'Accept-Language: en-US,en;q=0.5' \
+ -H 'Connection: keep-alive' \
+ -H "Host: ${CERTREQ_CAHOST}" \
+ -H "Referer: ${CERTREQ_CA}/certsrv/certrqxt.asp" \
+ -H 'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko' \
+ -H 'Content-Type: application/x-www-form-urlencoded' )"
+ CURRENTNUM="$( echo "${RESPONSE}" | grep -cE 'Option' )"
+
+ # GET LATEST CA CERT CHAIN
+ CURRENT_P7B="$( curl -s -k -u "${CERTREQ_USER}:${CERTREQ_PASS}" --ntlm \
+ "${CERTREQ_CA}/certsrv/certnew.p7b?ReqID=CACert&Renewal=${CURRENTNUM}" \
+ -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
+ -H 'Accept-Encoding: gzip, deflate' \
+ -H 'Accept-Language: en-US,en;q=0.5' \
+ -H 'Connection: keep-alive' \
+ -H "Host: ${CERTREQ_CAHOST}" \
+ -H "Referer: ${CERTREQ_CA}/certsrv/certrqxt.asp" \
+ -H 'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko' \
+ -H 'Content-Type: application/x-www-form-urlencoded' )"
+
+ # CONVERT TO PEM
+ echo "${CURRENT_P7B}" | openssl pkcs7 -print_certs -out "${CERTREQ_TEMPFILE}"
+
+ # RENAME TO PROPER FILENAME
+ # will read only the first cert, so get domain of issuer of it.
+ CA_DOMAIN="$( openssl x509 -in "${CERTREQ_TEMPFILE}" -noout -issuer 2>&1 | sed -r -e 's/^.*CN=[A-Za-z0-9]+\.//;' )"
+ CHAIN_FILE="chain-${CA_DOMAIN}.crt"
+ mv -f "${CERTREQ_TEMPFILE}" "${CERTREQ_WORKDIR}/${CHAIN_FILE}" 1>/dev/null 2>&1
+
+} 1> ${logfile} 2>&1
+
+# CHECK EVERYTHING
+failed=0
+openssloutput="$( openssl x509 -in "${CERTREQ_WORKDIR}/${CERTREQ_CNPARAM}.crt" -noout -subject -issuer -startdate -enddate 2>/dev/null )"
+grep -qE -- 'REQUEST--' "${CERTREQ_WORKDIR}/${CERTREQ_CNPARAM}.crt" && failed=$(( failed + 1 ))
+grep -qiE '\<\/?body\>' "${CERTREQ_WORKDIR}/${CERTREQ_CNPARAM}.crt" && failed=$(( failed + 2 ))
+test ${finaloutput} -ne 0 && failed=$(( failed + 4 ))
+grep -qE -- '--END CERTIFICATE--' "${CERTREQ_WORKDIR}/${CERTREQ_CNPARAM}.crt" || failed=$(( failed + 8 ))
+#echo "${openssloutput}" | grep -qE "subject.*${CERTREQ_SUBJECT}" || failed=$(( failed + 16 ))
+echo "${openssloutput}" | grep -qE "issuer.*" || failed=$(( failed + 16 ))
+
+# if everything was successful, display information below
+#if test ${failed} -eq 0;
+#then
+ echo "workdir: ${CERTREQ_WORKDIR}"
+ echo "logfile: ${logfile}"
+ echo "certificate: ${CERTREQ_WORKDIR}/${CERTREQ_CNPARAM}.crt"
+ echo "key: ${CERTREQ_WORKDIR}/${CERTREQ_CNPARAM}.key"
+ echo "chain: ${CERTREQ_WORKDIR}/${CHAIN_FILE}"
+#fi
+clean_certreq
+exit "${failed}"
+
+# EMAIL LOGFILE
+#${sendsh} ${sendopts} "${server} ${scriptfile} out" ${logfile} ${interestedparties}
diff --git a/files/framework.sh b/files/framework.sh
new file mode 100755
index 0000000..9ef9470
--- /dev/null
+++ b/files/framework.sh
@@ -0,0 +1,406 @@
+#!/bin/sh
+# File: /tmp/framework.sh
+# Author: bgstack15@gmail.com
+# Startdate: 2014-06-02 15:22
+# Title: Framework for Common Elements in My Scripts
+# Purpose: Library of common script elements
+# Package: deployed by ansible
+# History: fv2017-10-09a=fi2017-10-10x
+# 2017-10-09a Stripped down for custom OL7 package version 1p3p1
+# 2017-11-17a Deployed via ansible
+# Usage: dot-source this script in ftemplate.sh used by newscript.sh
+# Reference:
+# Improve:
+fversion="2017-11-17a"
+
+# DEFINE FUNCTIONS
+
+isflag() {
+ # input: $1=word to parse
+ case "$1" in
+ --*) retval=2;;
+ -*) retval=1;;
+ *) retval=0;;
+ esac
+ echo $retval
+}
+
+parseParam() {
+ # determines if --longname or -shortflagS that need individual parsing
+ trimParam=$( printf '%s' "${param}" | sed -n 's/--//p' )
+ _rest=
+ if test -n "$trimParam";
+ then
+ parseFlag $trimParam
+ else
+ #splitShortStrings
+ _i=2
+ while test ${_i} -le ${#param};
+ do
+ _j=$( expr ${_i} + 1)
+ #_char=$(expr substr "$param" $_i 1)
+ #_rest=$(expr substr "$param" $_j 255)
+ _char=$( printf '%s' "${param}" | cut -c ${_i})
+ _rest=$( printf '%s' "${param}" | cut -c ${_j}-255)
+ parseFlag $_char
+ _i=$( expr ${_i} + 1)
+ done
+ fi
+}
+
+getval() {
+ tempval=
+ if test -n "${_rest}";
+ then
+ tempval="${_rest}"
+ hasval=1
+ _i=255 # skip rest of splitShortStrings because found the value!
+ elif test -n "$nextparam" && test $(isflag "$nextparam") -eq 0;
+ then
+ tempval="$nextparam"
+ hasval=1 #DNE; is affected by ftemplate!
+ paramnum=$nextparamnum
+ fi
+}
+
+debuglev() {
+ # call: debuglev 5 && ferror "debug level is at least a five!"
+ # added 2015-11-17
+ localdebug=0; localcheck=0;
+ fisnum ${debug} && localdebug=${debug}
+ fisnum ${1} && localcheck=${1}
+ test $localdebug -ge $localcheck && return 0 || return 1
+}
+
+fisnum() {
+ # call: fisnum $1 && debug=$1 || debug=10
+ fisnum=;
+ case $1 in
+ ''|*[!0-9]*) fisnum=1;; # invalid
+ *) fisnum=0;; # valid number
+ esac
+ return ${fisnum}
+}
+
+fistruthy() {
+ # call: if fistruthy "$val"; then
+ local _return=
+ case "$( echo "${1}" | tr '[:upper:]' '[:lower:]' )" in
+ yes|1|y|true|always) _return=true;;
+ esac
+ test -n "${_return}"; return $?
+}
+
+setval() {
+ # call: setval 0 value1 value2 value3 ... <<EOFOPTIONS
+ # /bin/foo1 --optforfoo1
+ # /usr/bin/foo2 --optforfoo2
+ # EOFOPTIONS
+ # ^ 0 = soft fail, 1 = critical-fail
+ quitonfail="${1}"; shift
+ _vars="${@}"
+ #echo "_vars=${_vars}"
+ _varcount=0
+ for _word in ${_vars}; do _varcount=$( expr $_varcount + 1 ); eval "_var${_varcount}=${_word}"; done
+ _usethis=0
+ while read line;
+ do
+ _varcount=0
+ if test ! "${_usethis}x" = "0x"; then break; fi
+ #echo "line=${line}";
+ for _word in ${line};
+ do
+ _varcount=$( expr $_varcount + 1 )
+ #echo "word ${_varcount}=${_word}";
+ case "${_varcount}" in
+ 1)
+ #echo "Testing for existence of file ${_word}"
+ if test -f "${_word}";
+ then
+ _usethis=1
+ #echo "${_var1}=${_word}"
+ eval "${_var1}=${_word}"
+ fi
+ ;;
+ *)
+ #echo "just an option: ${_word}"
+ if test "${_usethis}x" = "1x";
+ then
+ #eval echo "\${_var${_varcount}}=${_word}"
+ eval eval "\${_var${_varcount}}=${_word}"
+ fi
+ ;;
+ esac
+ done
+ done
+ #eval echo "testfile=\$${_var1}"
+ eval _testfile=\$${_var1}
+ if test ! -f "${_testfile}";
+ then
+ case "${quitonfail}" in 1) _failval="critical-fail";; *) _failval="fail";; esac
+ eval "${_var1}=${_failval}"
+ setvalout=${_failval}
+ else
+ eval setvalout="valid-${_var1}"
+ fi
+}
+
+flecho() {
+ if test "$lechoscript" = ""; #so only run the first time!
+ then
+ setval 0 lechoscript << EOFLECHOSCRIPTS
+./plecho.sh
+${scriptdir}/plecho.sh
+~/bin/bgscripts/plecho.sh
+~/bin/plecho.sh
+~/bgscripts/plecho.sh
+~/plecho.sh
+/usr/local/bin/bgscripts/plecho.sh
+/usr/local/bin/plecho.sh
+/usr/bin/bgscripts/plecho.sh
+/usr/bin/plecho.sh
+/usr/bin/plecho
+/bin/bgscripts/plecho.sh
+/usr/share/bgscripts/plecho.sh
+EOFLECHOSCRIPTS
+ lechoscriptvalid="${setvalout}"
+ fi
+ if test "$lechoscriptvalid" = "valid-lechoscript";
+ then
+ $lechoscript "$@"
+ else
+ #ORIGINAL echo [`date '+%Y-%m-%d %T'`]$USER@`uname -n`: "$*"
+ myflecho() {
+ mflnow=$( date '+%Y-%m-%d %T' )
+ while test -z "$1" && test -n "$@"; do shift; done
+ sisko="$@"
+ test -z "$sisko" && \
+ printf "[%19s]%s@%s\n" "$mflnow" "$USER" "$server" || \
+ printf "[%19s]%s@%s: %s\n" "$mflnow" "$USER" "$server" "${sisko#" *"}" ;
+ }
+ if test ! -t 0; # observe that this is different from plecho
+ then
+ _x=0
+ while read line;
+ do
+ _x=1
+ myflecho "$@" "$line"
+ done
+ test $_x -eq 0 && myflecho "$@" "$line"
+ else
+ myflecho "$@"
+ fi
+ fi
+}
+
+fwhich() {
+ # call: fwhich $infile1
+ # returns the fqdn of files
+ readlink -f "$@"
+}
+
+ferror() {
+ # call: ferror "$scriptfile: 2. Something bad happened-- error message 2."
+ echo "$@" 1>&2
+}
+
+linecat() {
+ # call: linecat "foo" "bar"
+ # output: foobar
+ printf "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "${10}" "${11}" "${12}" "${13}" "${14}" "${15}"
+}
+
+prepend() { while read prependinput; do linecat "$@" "$prependinput"; done; }
+append() { while read appendinput; do linecat "$appendinput" "$@"; done; }
+
+setmailopts() {
+ setval 0 sendsh sendopts<<EOFSENDSH
+./send.sh -hs
+${scriptdir}/send.sh -hs
+~/bin/bgscripts/send.sh -hs
+~/bin/send.sh -hs
+~/bgscripts/send.sh -hs
+~/send.sh -hs
+/usr/local/bin/bgscripts/send.sh -hs
+/usr/local/bin/send.sh -hs
+/usr/bin/bgscripts/send.sh -hs
+/usr/bin/send.sh -hs
+/usr/send.sh -hs
+/bin/bgscripts/send.sh -hs
+/usr/share/bgscripts/send.sh -hs
+/usr/bin/mail -s
+EOFSENDSH
+}
+
+setdebug() {
+ # call: setdebug
+ debug=10
+ getval
+ if test $hasval -eq 1;
+ then
+ if fisnum ${tempval};
+ then
+ debug=${tempval}
+ else
+ #test paramnum -le paramcount && paramnum=$( expr ${paramnum} - 1 )
+ hasval=0
+ fi
+ elif fisnum ${_rest};
+ then
+ debug=${_rest}
+ _i=255
+ else
+ test $paramnum -le $paramcount && test -z ${nextparam} && paramnum=$( expr ${paramnum} - 1 )
+ fi
+}
+
+isvalidip() {
+ # call: if isvalidip "${input}"; then echo yes; fi
+ # or: isvalidip $input && echo yes
+ iptotest="${1}"
+ echo "${iptotest}" | grep -qoE "^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})$"
+}
+
+get_conf() {
+ # call: get_conf "${conffile}"
+ local _infile="$1"
+ local _tmpfile1="$( mktemp )"
+ sed -e 's/^\s*//;s/\s*$//;/^[#$]/d;s/\s*[^\]#.*$//;' "${_infile}" | grep -viE "^$" | while read _line;
+ do
+ local _left="$( echo "${_line}" | cut -d'=' -f1 )"
+ eval "_thisval=\"\${${_left}}\""
+ test -z "${_thisval}" && echo "${_line}" >> "${_tmpfile1}"
+ done
+ test -f "${_tmpfile1}" && { . "${_tmpfile1}" 1>/dev/null 2>&1; debuglev 10 && cat "${_tmpfile1}" 1>&2; }
+ /bin/rm -rf "${_tmpfile1}" 1>/dev/null 2>&1
+}
+
+define_if_new() {
+ # call: define_if_new IFW_IN_LOG_FILE "/var/log/messages"
+ eval thisval="\${${1}}"
+ test -z "${thisval}" && eval "$1"=\"$2\"
+}
+
+# INITIALIZE VARIABLES
+#infile1=
+#outfile1=
+#logfile=
+today=$( date '+%Y-%m-%d' )
+now=$( date '+%Y-%m-%d %T' )
+server=$( hostname -s )
+thistty=$( tty )
+thisip=$( ifconfig 2>/dev/null | awk '/Bcast|broadcast/{print $2}' | tr -cd '[^0-9\.\n]' | head -n1 )
+thisos="$( uname -s )"
+# get thisflavor and thisflavorversion. Examples: centos, ubuntu, redhat
+if test -f /etc/os-release;
+then
+ eval thisflavor=$( grep -iE "^\s*ID=" /etc/os-release 2>/dev/null | sed 's/^.*=//;' | tr 'A-Z' 'a-z' )
+ eval thisflavorversion=$( grep -iE "^\s*PRETTY_NAME=" /etc/os-release 2>/dev/null | sed -e 's/^.*=//;' | tr -dc '0-9.' )
+elif test -f /etc/system-release && test $( wc -l < /etc/system-release 2>/dev/null ) -eq 1;
+then
+ eval thisflavor=$( awk '{print $1}' < /etc/system-release 2>/dev/null | tr 'A-Z' 'a-z' )
+ eval thisflavorversion=$( </etc/system-release sed -e 's/^.*=//;' 2>/dev/null | tr -dc '0-9.' )
+else
+ if test "${thisos}" = "FreeBSD"; then
+ thisflavor="$( uname -i )"; thisflavorversion="$( uname -r )";
+ else
+ thisflavor="other"
+ thisflavorversion="unknown"
+ fi
+fi
+case "${thisos}" in FreeBSD) sed=gsed;; *) sed=sed;; esac
+
+# if framework is dot sourced then $0 will be "-bash" and screw things up
+case ${0} in
+ "-bash")
+ scriptdir="$( pwd )"
+ scriptfile="dot-sourced";;
+ *)
+ scriptdir="$( cd $( dirname ${0} ); pwd )"
+ scriptfile="$( basename ${0} | sed 's!/./!/!g;s!\./!!g' )"
+ scripttrim="${scriptfile%%.sh}"
+ ;;
+esac
+
+# SPECIAL RUNTIME-RELATED VARIABLES
+thisppid=$( ps -p $$ -o ppid | awk 'NR>1' | tr -d ' ' )
+cronpid=$( ps -ef | grep -E "/c[r]on" | grep -vE "grep.*-E.*cron|[0-9]\s*vi " | awk '{print $2}' )
+test "$cronpid" = "$thisppid" && is_cronjob=1
+test ! -t 0 && stdin_piped=1
+test ! -t 1 && stdout_piped=1
+test ! -t 2 && stderr_piped=1
+test "$( tty 2>&1 )" = "not a tty" && stdin_local=1
+test "$USER" = "root" && is_root=1
+test -n "$SUDO_USER" && is_root="sudo"
+
+nullflagcount=0
+validateparams() {
+ # VALIDATE PARAMETERS
+ # scroll through all parameters and check for isflag.
+ # if isflag, get all flags listed. Also grab param#.
+ paramcount=$#
+ thiscount=0;thisopt=0;freeopt=0;
+ varsyet=0
+ paramnum=0
+ debug=0
+ fallopts=
+ while test $paramnum -lt $paramcount;
+ do
+ paramnum=$( expr ${paramnum} + 1 )
+ eval param=\${$paramnum}
+ nextparamnum=$( expr ${paramnum} + 1 )
+ eval nextparam=\${$nextparamnum}
+ case $param in
+ "-")
+ if test "$varsyet" = "0";
+ then
+ # first instance marks beginning of flags and parameters.
+ #Until then it was the names of variables to fill.
+ varsyet=1
+ else
+ nullflagcount=$( expr ${nullflagcount} + 1 ) #useful for separating flags from something else?
+ debuglev 10 && ferror "null flag!" # second instance is null flag.
+ fi
+ ;;
+ esac
+ if test -n "$param";
+ then
+ # parameter $param exists.
+ if test $(isflag $param) -gt 0;
+ then
+ # IS FLAG
+ parseParam
+ else
+ # IS VALUE
+ if test "$varsyet" = "0";
+ then
+ thisopt=$( expr ${thisopt} + 1 )
+ test "${param}" = "DEBUG" && debug=10 && thisopt=$( expr ${thisopt} - 1 ) || \
+ eval "varname${thisopt}=${param}"
+ #varname[${thisopt}]="${param}"
+ debuglev 10 && ferror "var \"${param}\" named"
+ else
+ thiscount=$( expr ${thiscount} + 1 )
+ test $thiscount -gt $thisopt && freeopt=$( expr ${freeopt} + 1 )
+ #eval ${varname[${thiscount}]:-opt${freeopt}}="\"${param}\""
+ eval "thisvarname=\${varname${thiscount}}"
+ test -z "${thisvarname}" && eval "thisvarname=opt${freeopt}"
+ eval "${thisvarname}=\"${param}\""
+ eval fallopts=\"${fallopts} ${param}\"
+ debuglev 10 && ferror "${thisvarname} value: ${param}"
+ fi
+ fi
+ fi
+ done
+ fallopts="${fallopts# }"
+ if debuglev 10;
+ then
+ ferror "thiscount=$thiscount"
+ ferror "fallopts=$fallopts"
+ ferror "Framework $fversion"
+ ferror "Finput $fiversion"
+ fi
+}
+
+# PROCEDURAL SECTION ( NOT MAIN HOWEVER; THIS IS STILL LIBRARY )
+echo " $@ " | grep -qiE -- "--fcheck" 1>/dev/null 2>&1 && { echo "$fversion" | sed 's/[^0-9]//g;'; } || setmailopts
diff --git a/handlers/main.yml b/handlers/main.yml
new file mode 100644
index 0000000..1c4a4e5
--- /dev/null
+++ b/handlers/main.yml
@@ -0,0 +1,8 @@
+---
+# handlers for ansible role: certreq
+
+- name: handler for XYZ app
+ debug:
+ msg: "You called this handler!"
+
+...
diff --git a/main.yml b/main.yml
new file mode 100644
index 0000000..839866a
--- /dev/null
+++ b/main.yml
@@ -0,0 +1,19 @@
+---
+# File: /etc/ansible/roles/certreq/main.yml
+# Author: bgstack15
+# Startdate: 2017-09-13
+# Title: Role that Generates a PKCS12 Certificate File From a Microsoft Sub-CA
+# Purpose: Generates a cert from a Microsoft Sub-CA
+# History:
+# Usage:
+# Reference:
+# /etc/ansible/configuration/test/certreq.yml
+# Improve:
+# Documentation: README.txt
+
+- hosts: all
+ tasks:
+ - include: tasks/main.yml
+ handlers:
+ - include: handlers/main.yml
+...
diff --git a/tasks/1_certreq.yml b/tasks/1_certreq.yml
new file mode 100644
index 0000000..e4f06d7
--- /dev/null
+++ b/tasks/1_certreq.yml
@@ -0,0 +1,41 @@
+---
+# Reference: Use template name, not "template display name" https://social.technet.microsoft.com/Forums/en-US/d5cafc77-3376-43ca-94fd-6b07f7cb193f/using-certutilcertreq-to-get-sccm-client-certs-nondomain-clients?forum=configmgrgeneral
+
+## read in custom variable, based on domain of the host
+- name: read which CA to work with
+ include_vars: "{{ item }}"
+ with_first_found:
+ - '{{ ansible_dns.search[0] }}.yml'
+ - 'prod1.yml'
+ no_log: true
+
+- name: deploy dependencies
+ copy:
+ src: "{{ item.f }}"
+ dest: "/tmp/{{ item.f }}"
+ mode: "{{ item.m }}"
+ owner: root
+ group: root
+ with_items:
+ - { f: 'framework.sh', m: '0755' }
+ - { f: 'certreq.conf', m: '0644' }
+ changed_when: false
+
+- name: request certificate
+ script: certreq.sh -c /tmp/certreq.conf -u "{{ ca_user }}" -p "{{ ca_pass }}" -t "{{ ca_template }}" --ca "{{ ca_host }}"
+ register: certreq
+
+- debug:
+ msg: "{{ certreq }}"
+
+#- pause:
+# prompt: 'DOES THE ABOVE LOOK OK?...................'
+
+- name: cleanup
+ file:
+ path: "/tmp/{{ item.f }}"
+ state: absent
+ with_items:
+ - { f: 'framework.sh', m: '0755' }
+ - { f: 'certreq.conf', m: '0644' }
+ changed_when: false
diff --git a/tasks/2_generate_pfx.yml b/tasks/2_generate_pfx.yml
new file mode 100644
index 0000000..ec45282
--- /dev/null
+++ b/tasks/2_generate_pfx.yml
@@ -0,0 +1,29 @@
+---
+# Reference: https://bgstack15.wordpress.com/2016/06/30/manipulating-ssl-certificates/
+
+- name: save private key file as variable
+ shell: warn=no echo "{{ certreq.stdout }}" | awk '/^key:/{print $2;}'
+ register: privatekey
+ failed_when: privatekey.stdout_lines | length != 1
+ changed_when: false
+# privatekey.stdout
+
+- name: save public key file as variable
+ shell: warn=no echo "{{ certreq.stdout }}" | awk '/^certificate:/{print $2;}'
+ register: publickey
+ failed_when: publickey.stdout_lines | length != 1
+ changed_when: false
+# publickey.stdout
+
+- name: save cert chain file as variable
+ shell: warn=no echo "{{ certreq.stdout }}" | awk '/^chain:/{print $2;}'
+ register: chain
+ failed_when: chain.stdout_lines | length != 1
+ changed_when: false
+# chain.stdout
+
+- name: generate pkcs12 file
+ shell: warn=no openssl pkcs12 -export -in "{{ publickey.stdout }}" -inkey "{{ privatekey.stdout }}" -out /tmp/certnew.pfx -CAfile "{{ chain.stdout }}" -certfile "{{ chain.stdout }}" -passin pass:'' -passout pass:''
+ register: pfx
+
+...
diff --git a/tasks/main.yml b/tasks/main.yml
new file mode 100644
index 0000000..36de385
--- /dev/null
+++ b/tasks/main.yml
@@ -0,0 +1,9 @@
+---
+- block:
+ #- ping:
+ - include_tasks: 1_certreq.yml
+ - include_tasks: 2_generate_pfx.yml
+ become: yes
+ become_user: root
+ become_method: sudo
+...
diff --git a/vars/prod1.example.com.yml.example b/vars/prod1.example.com.yml.example
new file mode 120000
index 0000000..ac20d69
--- /dev/null
+++ b/vars/prod1.example.com.yml.example
@@ -0,0 +1 @@
+prod1.yml.example \ No newline at end of file
diff --git a/vars/prod1.yml.example b/vars/prod1.yml.example
new file mode 100644
index 0000000..aa6e215
--- /dev/null
+++ b/vars/prod1.yml.example
@@ -0,0 +1,5 @@
+domain: prod1.example.com
+ca_host: ca2.prod1.example.com
+ca_user: user
+ca_pass: ExamplePW55*
+ca_template: "ConfigMgrLinuxClientCertificate"
bgstack15