Add an HTTP hook for updating code on the puppet server
authorJan Dittberner <jandd@cacert.org>
Sat, 14 Apr 2018 18:07:19 +0000 (20:07 +0200)
committerJan Dittberner <jandd@cacert.org>
Sat, 14 Apr 2018 18:07:19 +0000 (20:07 +0200)
hieradata/nodes/puppet.yaml
sitemodules/profiles/files/puppet_server/git-pull-hook [new file with mode: 0755]
sitemodules/profiles/files/puppet_server/git-pull-hook.init.sh [new file with mode: 0644]
sitemodules/profiles/manifests/puppet_server.pp [new file with mode: 0644]
sitemodules/profiles/templates/puppet_server/git-pull-hook.ini.epp [new file with mode: 0644]
sitemodules/roles/manifests/puppetmaster.pp

index 02eee27..6cb160e 100644 (file)
@@ -3,3 +3,16 @@ classes:
   - roles::puppetmaster
 profiles::base::admins:
   - jandd
+profiles::puppet_server::git_pull_ssh_passphrase: >
+    ENC[PKCS7,MIIBmQYJKoZIhvcNAQcDoIIBijCCAYYCAQAxggEhMIIBHQIBADAFMAACAQEw
+    DQYJKoZIhvcNAQEBBQAEggEAhbUQYK6aL9A43CJJoXTpgpEvKny739PCf4pQ
+    pn4hkzJPV+j8+9AOIMPF9Jl4ZWJP1nClEia7/or5/ACeDzs1mIDejf7OXW2o
+    nBV2QQs34VgFTNyD+szaouftcPJK25/2/EwGzn7XCnefd7xiEd1xxtogDHTp
+    VDxcZMuA1/OJ7PYSkYkzRPzwHKUuqqOl2uUPZquOpQgJEYC7lkDePNd8zFvp
+    XmXjaL27EhXyn3tvAjytHOhSU24F0xspK0+Xuv46AIQLztMFO5MItefUQrXF
+    xSUdHR1h558pJf45YRxT9nmiDLdFUt8hLl0aUep7+kkG+7pSytQJT8GTSTFb
+    0MlwuDBcBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBDX/ycL+PdDfsZ4QGIz
+    72sBgDCNDoJmkzzjSfLIvN/Q2D0p2XBtKWrc7NkmVzZrzVZ6cLJCBornuJ72
+    fOJnmPqpFng=]
+profiles::puppet_server::git_pull_tokens:
+  - ENC[PKCS7,MIIBmQYJKoZIhvcNAQcDoIIBijCCAYYCAQAxggEhMIIBHQIBADAFMAACAQEwDQYJKoZIhvcNAQEBBQAEggEAWLS320bvmv2NSEtEFn5dFD5goJ2g3T2JDT8EylyZ5WDMdlW3w8kx4cQSju1TvjrUIRimkYMKIi9Ej2cXUEMZZoWXpdTbxBreBY5NNlzz0UWmzuwgx6qVy3Czlvhbqq2vb97W1/e30sKcaXe4Mly9UWy+pslH3KHEs4vwipZ+MaIlY76HOPVUmQBtNwaaIiY2KtBTfVGXGUp5VXYGQZsUL2K6DucT+xP3duYjK5iCPfk+w/iYz1bv3m2uT5GvsahZhCBGJNu81ZZ9MY4+WZfKwqfEN2fQz1X6mqBvpaXqE3w573SZo6K7ofhiK8q6+4QlNJ6qlLrsuetlgu8+aHYhcDBcBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBDoG8fxzhnn0Xp4Bk+Yg4RsgDDTTKABVrszTg2gZ1bZPm0ysvpoG6tSE0FVmexQjR814RSl81f7coXap/pnWwAhpPo=]
\ No newline at end of file
diff --git a/sitemodules/profiles/files/puppet_server/git-pull-hook b/sitemodules/profiles/files/puppet_server/git-pull-hook
new file mode 100755 (executable)
index 0000000..d8761f5
--- /dev/null
@@ -0,0 +1,94 @@
+#!/usr/bin/env python3
+
+"""
+This script takes care of updating the code in a directory by performing
+git pull when triggered via an HTTP request to port 8000.
+
+The script needs the sshpass and git packages installed.
+
+Configuration is read from /etc/git-pull-hook.ini, ~/.git-pull-hook.ini and
+a git-pull-hook.ini in the working directory in that order.
+"""
+
+from configparser import ConfigParser
+from http import HTTPStatus
+from http.server import HTTPServer, BaseHTTPRequestHandler
+import os
+from subprocess import Popen, PIPE
+
+ENV_FOR_GIT = {
+    'PATH': '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
+}
+
+TOKENS = []
+GIT_DIRECTORY = ""
+
+
+def read_ini():
+    global ENV_FOR_GIT, TOKENS, GIT_DIRECTORY
+    config = ConfigParser()
+    config.read(['/etc/git-pull-hook.ini',
+                 os.path.expanduser('~/.git-pull-hook.ini'),
+                 'git-pull-hook.ini'])
+    ENV_FOR_GIT['SSHPASS'] = config['git-pull-hook']['ssh_passphrase']
+    TOKENS = [token.strip() for token in
+              config['git-pull-hook']['tokens'].split(',')]
+    GIT_DIRECTORY = config['git-pull-hook']['git_directory']
+
+
+class GitHookRequestHandler(BaseHTTPRequestHandler):
+    """
+    Custom HTTP request handler for updating a git repository when called
+    with a known authentication token in an "Authentication" HTTP header.
+    """
+
+    def _handle_pull(self):
+        try:
+            git_proc = Popen(
+                ['sshpass', '-e', '-P', 'passphrase', 'git', 'pull'],
+                env=ENV_FOR_GIT, cwd=GIT_DIRECTORY, stdout=PIPE, stderr=PIPE)
+            stdout, stderr = git_proc.communicate()
+            for line in stderr.decode('UTF-8').splitlines():
+                self.log_error('git: %s', line)
+            for line in stdout.decode('UTF-8').splitlines():
+                self.log_message('git: %s', line)
+        except Exception as e:
+            self.log_error("Could not pull changes for %s: %s",
+                           GIT_DIRECTORY, e)
+        self.send_response(HTTPStatus.OK)
+        self.send_header('Content-Type', 'text/plain; charset=utf8')
+        self.flush_headers()
+        self.wfile.write(("updated %s" % GIT_DIRECTORY).encode('UTF-8'))
+
+    # noinspection PyPep8Naming
+    def do_GET(self):
+        """
+        Handle GET requests, requests to /health are allowed for every caller,
+        requests to / need a valid token in the "Authentication" HTTP header
+        and trigger a git pull in the configured directory.
+        """
+        if self.path == '/':
+            if self.headers['Authentication'] in [token for token in TOKENS]:
+                self._handle_pull()
+            else:
+                self.send_response(HTTPStatus.UNAUTHORIZED)
+                self.flush_headers()
+        elif self.path == '/health':
+            self.send_response(HTTPStatus.OK)
+            self.flush_headers()
+            self.wfile.write(b"I'm healthy!")
+        else:
+            self.send_error(HTTPStatus.BAD_REQUEST)
+            self.flush_headers()
+            self.wfile.write(b"You requested something I do not understand")
+
+
+def run(server_class=HTTPServer, handler_class=GitHookRequestHandler):
+    server_address = ('', 8000)
+    httpd = server_class(server_address, handler_class)
+    httpd.serve_forever()
+
+
+if __name__ == '__main__':
+    read_ini()
+    run()
diff --git a/sitemodules/profiles/files/puppet_server/git-pull-hook.init.sh b/sitemodules/profiles/files/puppet_server/git-pull-hook.init.sh
new file mode 100644 (file)
index 0000000..7974a9f
--- /dev/null
@@ -0,0 +1,86 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides:          git-pull-hook
+# Required-Start:    $remote_fs
+# Required-Stop:     $remote_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: CAcert puppet git pull hook
+# Description:       CAcert puppet git pull hook
+### END INIT INFO
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+
+BASE=git-pull-hook
+
+GIT_PULL_HOOK=/usr/local/sbin/git-pull-hook
+GIT_PULL_HOOK_PIDFILE=/var/run/$BASE.pid
+GIT_PULL_HOOK_LOGFILE=/var/log/$BASE.log
+GIT_PULL_HOOK_DESC="Puppet git pull hook"
+
+# Get lsb functions
+. /lib/lsb/init-functions
+
+# Check git-pull-hook is present
+if [ ! -x $GIT_PULL_HOOK ]; then
+  log_failure_msg "$GIT_PULL_HOOK not present or not executable"
+  exit 1
+fi
+
+fail_unless_root() {
+  if [ "$(id -u)" != '0' ]; then
+    log_failure_msg "$GIT_PULL_HOOK_DESC must be run as root"
+    exit 1
+  fi
+}
+
+case "$1" in
+  start)
+    fail_unless_root
+
+    touch "$GIT_PULL_HOOK_LOGFILE"
+    chown root:adm "$GIT_PULL_HOOK_LOGFILE"
+
+    log_begin_msg "Starting $GIT_PULL_HOOK_DESC: $GIT_PULL_HOOK"
+    start-stop-daemon --start --background --no-close \
+      --exec "$GIT_PULL_HOOK" \
+      --pidfile "$GIT_PULL_HOOK_PIDFILE" \
+      --chdir "/" \
+      --make-pidfile \
+         >> "$GIT_PULL_HOOK_LOGFILE" 2>&1
+    log_end_msg $?
+    ;;
+
+  stop)
+    fail_unless_root
+    if [ -f "$GIT_PULL_HOOK_PIDFILE" ]; then
+      start-stop-daemon --stop --pidfile "$GIT_PULL_HOOK_PIDFILE" --retry 5
+      log_end_msg $?
+    else
+      log_warning_msg "$GIT_PULL_HOOK_DESC already stopped - file $GIT_PULL_HOOK_PIDFILE not found."
+    fi
+    ;;
+
+  restart)
+    fail_unless_root
+  git_pull_hook_pid=`cat "$GIT_PULL_HOOK_PIDFILE" 2> /dev/null`
+  [ -n "$git_pull_hook_pid" ] \
+    && ps -p $git_pull_hook_pid > /dev/null 2>&1 \
+    && $0 stop
+  $0 start
+  ;;
+
+  force-reload)
+    fail_unless_root
+    $0 restart
+    ;;
+
+  status)
+    status_of_proc -p "$GIT_PULL_HOOK_PIDFILE" "$GIT_PULL_HOOK" "$GIT_PULL_HOOK_DESC"
+    ;;
+
+  *)
+    echo "Usage: service git-pull-hook {start|stop|restart|force-reload|status}"
+    exit 1
+    ;;
+esac
diff --git a/sitemodules/profiles/manifests/puppet_server.pp b/sitemodules/profiles/manifests/puppet_server.pp
new file mode 100644 (file)
index 0000000..cf7bc9a
--- /dev/null
@@ -0,0 +1,86 @@
+# Class: profiles::puppet_server
+# ==============================
+#
+# This class takes care of resources on the puppet server
+#
+# Parameters
+# ----------
+#
+# @param git_pull_ssh_passphrase passphrase to use for the ssh key to pull
+#                                new code from the control repository
+# @param git_pull_directory      directory where the puppet control repository
+#                                is checked out
+# @param git_pull_tokens         list of tokens that are valid to trigger the
+#                                git pull hook
+#
+# Examples
+# --------
+#
+# @example
+#   class roles::myhost {
+#     include profiles::puppet_server
+#   }
+#
+# Authors
+# -------
+#
+# Jan Dittberner <jandd@cacert.org>
+#
+# Copyright
+# ---------
+#
+# Copyright 2018 Jan Dittberner
+class profiles::puppet_server (
+  String $git_pull_ssh_passphrase,
+  String $git_pull_directory = '/etc/puppetlabs/code/environment/production',
+  Array[String] $git_pull_tokens,
+) {
+  package { 'sshpass':
+    ensure => installed,
+  }
+
+  package { 'git':
+    ensure => installed,
+  }
+
+  file { '/usr/local/sbin/git-pull-hook':
+    ensure  => file,
+    owner   => 'root',
+    group   => 'root',
+    mode    => '0750',
+    source  => 'puppet:///modules/profiles/puppet_server/git-pull-hook',
+    require => [Package['sshpass'], Package['git']],
+  }
+
+  file { '/etc/init.d/git-pull-hook':
+    ensure => file,
+    owner  => 'root',
+    group  => 'root',
+    mode   => '0755',
+    source => 'puppet:///modules/profiles/puppet_server/git-pull-hook.init.sh'
+  }
+
+  file { '/etc/git-pull-hook.ini':
+    ensure  => file,
+    owner   => 'root',
+    group   => 'root',
+    mode    => '0400',
+    content => epp(
+      'profiles/puppet_server/git-pull-hook.ini.epp',
+      {
+        'ssh_passphrase' => $git_pull_ssh_passphrase,
+        'tokens'         => $git_pull_tokens,
+        'git_directory'  => $git_pull_directory,
+      }
+    )
+  }
+
+  service { 'git-pull-hook':
+    ensure  => running,
+    enable  => true,
+    require => [
+      File['/etc/init.d/git-pull-hook'], File['/usr/local/sbin/git-pull-hook'],
+      File['/etc/git-pull-hook.ini'],
+    ],
+  }
+}
\ No newline at end of file
diff --git a/sitemodules/profiles/templates/puppet_server/git-pull-hook.ini.epp b/sitemodules/profiles/templates/puppet_server/git-pull-hook.ini.epp
new file mode 100644 (file)
index 0000000..8d292b2
--- /dev/null
@@ -0,0 +1,8 @@
+<%- | String $ssh_passphrase = undef, String $git_directory = undef, Array[String] $tokens = undef | -%>
+# THIS FILE IS MANAGED BY PUPPET, MANUAL CHANGES WILL BE OVERWRITTEN AT THE
+# NEXT PUPPET RUN.
+
+[git-pull-hook]
+ssh_passphrase=<%= $ssh_passphrase %>
+tokens=<%= $tokens.join(',') %>
+git_directory=<%= $git_directory %>
index ca3b867..db57744 100644 (file)
@@ -24,4 +24,5 @@ class roles::puppetmaster {
   include profiles::base
   include profiles::rsyslog
   include profiles::nrpe_agent
+  include profiles::puppet_server
 }