diff --git a/files/common-scripts/lock-wrapper.sh b/files/common-scripts/lock-wrapper.sh new file mode 100755 index 0000000000..f990f635eb --- /dev/null +++ b/files/common-scripts/lock-wrapper.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +if [ $# -lt 2 ]; then + echo "Usage: $0 [name] [script]" + exit 1; +fi + +NAME=$1 +SCRIPT=$2 + +LOCKDIR="/var/tmp/$NAME" +PIDFILE="$LOCKDIR/pid" + +function cleanup { + rm -rf "$LOCKDIR" +} + +RESTORE_UMASK=$(umask -p) +umask 0077 +if ! mkdir "$LOCKDIR"; then + echo "$LOCKDIR already exists" + PID=$(cat "$PIDFILE") + if [ -n "$PID" ] && /bin/ps $PID > /dev/null + then + echo "$PID is still running" + /bin/ps -o user,pid,start,time,comm $PID + exit 1; + else + echo "$LOCKDIR exists but $PID is dead" + echo "Removing lockdir and re-running" + /bin/rm -rf $LOCKDIR + mkdir $LOCKDIR || exit + fi +fi + +trap cleanup EXIT SIGQUIT SIGHUP SIGTERM +echo $$ > "$PIDFILE" + +$RESTORE_UMASK +eval "$SCRIPT" + diff --git a/files/common-scripts/nag-once b/files/common-scripts/nag-once new file mode 100755 index 0000000000..992023cacf --- /dev/null +++ b/files/common-scripts/nag-once @@ -0,0 +1,108 @@ +#!/usr/bin/python -tt +# nag once +# take stdin and arguments: commandid time-to-ignore-same-output +# if we encounter any kind of error just output whatever we're given +# and prepend our own errors +# so we don't hurt things any worse :) +# copyright (c) 2011 Red Hat, inc +# gpl v2 blah blah +# skvidal - skvidal@fedoraproject.org + +import tempfile +import sys +import time +import os +import stat +import glob + + +def translate_tti(tti): + # translate 1w, 2d, 3d, 1m, 2h, 55m etc to seconds here + + if tti is None: + return None + + seconds_per_unit = {"s": 1, "m": 60, "h": 3600, "d": 86400, "w": 604800,} + if tti.isdigit():# just seconds + return int(tti) + return int(tti[:-1]) * seconds_per_unit[tti[-1]] + + + +def get_cmdid_dir(cmdid): + """return a path to a valid and safe cachedir""" + tmpdir="/var/tmp" + prefix = "nag-once:" + cmdid + "#" + dirpath = '%s/%s*' % (tmpdir, prefix) + cachedirs = sorted(glob.glob(dirpath)) + for thisdir in cachedirs: + stats = os.lstat(thisdir) + if stat.S_ISDIR(stats[0]) and stat.S_IMODE(stats[0]) == 448 and stats[4] == os.getuid(): + return thisdir + + # make the dir (tempfile.mkdtemp()) + cachedir = tempfile.mkdtemp(prefix=prefix, dir=tmpdir) + return cachedir + + + +def main(): + + if len(sys.argv) < 2: + raise Exception("Usage: nag-once cmdid [time-to-ignore]") + + cmdid = sys.argv[1] + # go through and remove stupid shit from cmdid + cmdid = cmdid.replace('/', '') + cmdid = cmdid.replace('#', '') + cmdid = cmdid.replace('..', '') + cmdid = cmdid.replace(';', '') + cmdid = cmdid.replace('|', '') + cmdid = cmdid.replace('&', '') + cmdid = cmdid.replace('\\', '') + + now = time.time() + tti = None + if len(sys.argv) > 2: + tti = sys.argv[2] + + tti = translate_tti(tti) + + # make up or find our tempdir + mydir = get_cmdid_dir(cmdid) + + old_output = None + old_date = 0 + if os.path.exists(mydir + '/output'): + old_output = open(mydir + '/output').read() + old_date = os.lstat(mydir + '/output')[stat.ST_MTIME] + + # take from stdin + # + theinput = sys.stdin.read() + try: + # at this point we have to handle any outputs more ourself b/c we've just read + # out of sys.stdin :( + + if theinput != old_output or (tti and now - old_date > tti): + if theinput.strip(): # if there is nothing here, don't output and don't drop a \n on the end of it + print theinput, + fo = open(mydir + '/output', 'w') + fo.write(theinput) + fo.flush() + fo.close() + + + except Exception, e: + print >> sys.stderr, e + print >> sys.stderr, theinput + +if __name__ == '__main__': + try: + main() + except Exception, e: + print >> sys.stderr, e + if not sys.stdin.isatty(): + print >> sys.stderr, sys.stdin.read() + + diff --git a/files/fas-client/fas-client.cron b/files/fas-client/fas-client.cron new file mode 100644 index 0000000000..4ec50f9a8c --- /dev/null +++ b/files/fas-client/fas-client.cron @@ -0,0 +1 @@ +*/10 * * * * root /usr/local/bin/lock-wrapper fasClient "/bin/sleep $(($RANDOM \% 180)); /usr/bin/fasClient -i | /usr/local/bin/nag-once fassync 1d 2>&1" diff --git a/files/fas-client/fas.conf.j2 b/files/fas-client/fas.conf.j2 new file mode 100644 index 0000000000..e5c20eea02 --- /dev/null +++ b/files/fas-client/fas.conf.j2 @@ -0,0 +1,102 @@ +[global] +; url - Location to fas server +url = https://admin.fedoraproject.org/accounts/ + +; temp - Location to generate files while user creation process is happening +temp = /var/db + +{% if ansible_domain == 'qa.fedoraproject.org' %} +; Eventually, no box should use systems user to fasClient. systems has access +; to password hashes. But password hashes are needed for sudo unless the box +; uses pam_url +; login - username to contact fas +login = systems + +; password - password for login name +password = {{ systemsUserPassword }} +{% else %} +; login - username to contact fas +login = {{ fedorathirdpartyUser }} + +; password - password for login name +password = {{ fedorathirdpartyPassword }} +{% endif %} +; prefix - install to a location other than / +prefix = / + +; modefile - Location of a file containing saved home directory modes +modefile = /var/lib/fas/client_dir_perms + +; cla_group - Group for CLA requirements +cla_group = cla_done + +[host] +; Group hierarchy is 1) groups, 2) restricted_groups 3) ssh_restricted_groups +; so if someone is in all 3, the client behaves the same as if they were just +; in 'groups' + +; groups that should have a shell account on this system. +{% if fas_client_groups %} +groups = {{ fas_client_groups }} +{% else %} +groups = "sysadmin-main" +{% endif %} + +; groups that should have a restricted account on this system. +; restricted accounts use the restricted_shell value in [users] +restricted_groups = + +; ssh_restricted_groups: groups that should be restricted by ssh key. You will +; need to disable password based logins in order for this value to have any +; security meaning. Group types can be placed here as well, for example +; @hg,@git,@svn +{% if fas_client_ssh_groups %} +ssh_restricted_groups = {{ fas_client_ssh_groups }} +{% else %} +ssh_restricted_groups = +{% endif %} + +; aliases_template: Gets prepended to the aliases file when it is generated by +; fasClient +aliases_template = /etc/aliases.template + +[users] +; default shell given to people in [host] groups +shell = /bin/bash + +; home - the location for fas user home dirs +home = /home/fedora + +; home_backup_dir - Location home dirs should get moved to when a user is +; deleted this location should be tmpwatched +home_backup_dir = /home/fedora.bak + +; ssh_restricted_app - This is the path to the restricted shell script. It +; will not work automatically for most people though through alterations it +; is a powerfull way to restrict access to a machine. An alternative example +; could be given to people who should only have cvs access on the machine. +; setting this value to "/usr/bin/cvs server" would do this. +{% if fas_client_restricted_app %} +ssh_restricted_app = {{ fas_client_restricted_app }} +{% else %} +ssh_restricted_app = +{% endif %} + +; ssh_admin_app - This is the path to an app that an admin is allowed to use. +{% if fas_client_admin_app %} +ssh_admin_app = {{ fas_client_admin_app }} +{% else %} +ssh_admin_app = +{% endif %} + +; restricted_shell - The shell given to users in the ssh_restricted_groups +restricted_shell = /sbin/nologin + +; ssh_restricted_shell - The shell given to users in the ssh_restricted_groups +ssh_restricted_shell = /bin/bash + +; ssh_key_options - Options to be appended to people ssh keys. Users in the +; ssh_restricted_groups will have the keys they uploaded altered when they are +; installed on this machine, appended with the options below. +ssh_key_options = no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty + diff --git a/inventory/group_vars/arm-qa b/inventory/group_vars/arm-qa new file mode 100644 index 0000000000..90e2eced52 --- /dev/null +++ b/inventory/group_vars/arm-qa @@ -0,0 +1 @@ +fas_client_groups: "sysadmin-main,sysadmin-qa" diff --git a/inventory/group_vars/arm-releng b/inventory/group_vars/arm-releng new file mode 100644 index 0000000000..b7c416da98 --- /dev/null +++ b/inventory/group_vars/arm-releng @@ -0,0 +1 @@ +fas_client_groups: "sysadmin-main,sysadmin-releng" diff --git a/inventory/inventory b/inventory/inventory index 1fd1f36d75..f27285b91f 100644 --- a/inventory/inventory +++ b/inventory/inventory @@ -1,3 +1,11 @@ +[arm-releng] +arm03-releng01.arm.fedoraproject.org +arm03-releng02.arm.fedoraproject.org +arm03-releng03.arm.fedoraproject.org +arm03-releng04.arm.fedoraproject.org + +[arm-qa] +arm03-builder05.arm.fedoraproject.org [buildvm] buildvm-01.phx2.fedoraproject.org diff --git a/playbooks/groups/arm-qa.yml b/playbooks/groups/arm-qa.yml new file mode 100644 index 0000000000..5cfc7ae364 --- /dev/null +++ b/playbooks/groups/arm-qa.yml @@ -0,0 +1,17 @@ + +- name: Setup arm-qa hosts + hosts: arm-qa + user: root + gather_facts: False + tags: + - arm-qa + + vars_files: + - /srv/web/infra/ansible/vars/global.yml + - ${private}/vars.yml + + tasks: + # This group uses fas_client for user management + - include: $tasks/fas_client.yml + # This group includes our common scripts + - include: $tasks/common_scripts.yml diff --git a/playbooks/groups/arm-releng.yml b/playbooks/groups/arm-releng.yml new file mode 100644 index 0000000000..1a14b4d5bd --- /dev/null +++ b/playbooks/groups/arm-releng.yml @@ -0,0 +1,17 @@ + +- name: Setup arm-releng hosts + hosts: arm-releng + user: root + gather_facts: False + tags: + - arm-releng + + vars_files: + - /srv/web/infra/ansible/vars/global.yml + - ${private}/vars.yml + + tasks: + # This group uses fas_client for user management + - include: $tasks/fas_client.yml + # This group includes our common scripts + - include: $tasks/common_scripts.yml diff --git a/tasks/common_scripts.yml b/tasks/common_scripts.yml new file mode 100644 index 0000000000..6b4d268c94 --- /dev/null +++ b/tasks/common_scripts.yml @@ -0,0 +1,11 @@ +--- +# +# This task installs some common scripts to /usr/local/bin +# scripts are under $files/common-scripts +# + +- name: Install common scripts + action: copy src=$item dest=/usr/local/bin/ + with_fileglob: $files/common-scripts/* + tags: + - config diff --git a/tasks/fas_client.yml b/tasks/fas_client.yml new file mode 100644 index 0000000000..e61dfe7072 --- /dev/null +++ b/tasks/fas_client.yml @@ -0,0 +1,59 @@ +--- +# +# This task sets up fasClient on a machine. +# It installs the fas-clients package, then the /etc/fas.conf and finally a cron job update. +# + +# +# fas-clients is in the infrastructure repo. +# nss_db is needed to store user/group info. +# +- name: install package needed for fas-client + action: yum state=installed name=$item + with_items: + - fas-clients + tags: + - packages + +- name: install nss_db on rhel hosts only + action: yum state=installed name=nss_db + only_if: "'${ansible_distribution}' == 'RedHat'" + tags: + - packages + +# +# fasClients needs a valid /etc/fas.conf. +# There's vars used in this template: +# +# fas_client_groups = "sysadmin-main" +# fas_client_restricted_app = "" +# fas_client_admin_app = "" +# fas_client_ssh_groups = "" +# +# if desired, set them on a per host/group basis. +# +# Currently the default template is used, but could be modified on a host basis. +# +- name: setup /etc/fas.conf for client use + action: template src=$item dest=/etc/fas.conf owner=root mode=600 + with_first_found: + - $files/fas-client/${ansible_fqdn}.fas.conf.j2 + - $files/fas-client/${ansible_hostname}.fas.conf.j2 + - $files/fas-client/${ansible_hostname}.fas.conf.j2 + - $files/fas-client/fas.conf.j2 + tags: + - config + +# +# setup /etc/cron.d/ file to run sync every 10min +# TODO: use cron module when it's fixed +# +#- name: fas_client cron job +# cron: name="fas client" user=root cron_file=fas-client minute="*/10" job="/usr/bin/fasClient -i" +# tags: +# - config +# + - name: fas_client cron job + action: copy src=$files/fas-client/fas-client.cron dest=/etc/cron.d/fas-client owner=root mode=700 + tags: + - config