From 37f3607aec6545f63b8fd169ff055dee2b9077e8 Mon Sep 17 00:00:00 2001 From: Bodo Schulz Date: Tue, 16 Jan 2018 06:47:31 +0100 Subject: [PATCH 1/5] add local build and development for ruby-icinga-cert-service --- Dockerfile | 36 +- build/ruby-icinga-cert-service/Gemfile | 22 + build/ruby-icinga-cert-service/Gemfile.lock | 143 +++++ build/ruby-icinga-cert-service/LICENSE | 504 ++++++++++++++++++ build/ruby-icinga-cert-service/README.md | 296 ++++++++++ build/ruby-icinga-cert-service/Rakefile | 37 ++ .../bin/rest-service.rb | 268 ++++++++++ build/ruby-icinga-cert-service/bin/test.rb | 28 + .../icinga-cert-service.gemspec | 66 +++ .../init-script/openrc/icinga-cert-service | 59 ++ .../lib/cert-service.rb | 317 +++++++++++ .../lib/cert-service/certificate_handler.rb | 494 +++++++++++++++++ .../lib/cert-service/configure_icinga.rb | 1 + .../lib/cert-service/endpoint_handler.rb | 115 ++++ .../lib/cert-service/executor.rb | 32 ++ .../lib/cert-service/in-memory-cache.rb | 41 ++ .../lib/cert-service/monkey.rb | 39 ++ .../lib/cert-service/version.rb | 6 + .../lib/cert-service/zone_handler.rb | 63 +++ build/ruby-icinga-cert-service/lib/logging.rb | 37 ++ .../ruby-icinga-cert-service/lib/logging.rb~ | 41 ++ build/ruby-icinga-cert-service/lib/util.rb | 90 ++++ docker-compose_example.yml | 1 + rootfs/init/run.sh | 2 + rootfs/init/runtime/inotify.sh | 4 +- 25 files changed, 2730 insertions(+), 12 deletions(-) create mode 100644 build/ruby-icinga-cert-service/Gemfile create mode 100644 build/ruby-icinga-cert-service/Gemfile.lock create mode 100644 build/ruby-icinga-cert-service/LICENSE create mode 100644 build/ruby-icinga-cert-service/README.md create mode 100644 build/ruby-icinga-cert-service/Rakefile create mode 100755 build/ruby-icinga-cert-service/bin/rest-service.rb create mode 100755 build/ruby-icinga-cert-service/bin/test.rb create mode 100644 build/ruby-icinga-cert-service/icinga-cert-service.gemspec create mode 100644 build/ruby-icinga-cert-service/init-script/openrc/icinga-cert-service create mode 100644 build/ruby-icinga-cert-service/lib/cert-service.rb create mode 100644 build/ruby-icinga-cert-service/lib/cert-service/certificate_handler.rb create mode 100644 build/ruby-icinga-cert-service/lib/cert-service/configure_icinga.rb create mode 100644 build/ruby-icinga-cert-service/lib/cert-service/endpoint_handler.rb create mode 100644 build/ruby-icinga-cert-service/lib/cert-service/executor.rb create mode 100644 build/ruby-icinga-cert-service/lib/cert-service/in-memory-cache.rb create mode 100644 build/ruby-icinga-cert-service/lib/cert-service/monkey.rb create mode 100644 build/ruby-icinga-cert-service/lib/cert-service/version.rb create mode 100644 build/ruby-icinga-cert-service/lib/cert-service/zone_handler.rb create mode 100644 build/ruby-icinga-cert-service/lib/logging.rb create mode 100644 build/ruby-icinga-cert-service/lib/logging.rb~ create mode 100644 build/ruby-icinga-cert-service/lib/util.rb diff --git a/Dockerfile b/Dockerfile index d447f69..6389ac6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ ENV \ TERM=xterm \ TZ='Europe/Berlin' \ BUILD_DATE="2018-01-18" \ - BUILD_TYPE="stable" \ + BUILD_TYPE="local" \ CERT_SERVICE_VERSION="0.15.12" \ ICINGA_VERSION="2.8.0-r0" @@ -27,6 +27,8 @@ LABEL \ # --------------------------------------------------------------------------------------- +ADD build/ / + RUN \ apk update --quiet --no-cache && \ apk upgrade --quiet --no-cache && \ @@ -43,22 +45,34 @@ RUN \ chmod u+s /bin/busybox && \ echo 'gem: --no-document' >> /etc/gemrc && \ gem install --quiet --no-rdoc --no-ri \ - io-console bundler && \ + io-console bundler + +RUN \ + set -x && \ cd /tmp && \ - git clone https://github.com/bodsch/ruby-icinga-cert-service.git && \ - cd ruby-icinga-cert-service && \ - if [ "${BUILD_TYPE}" == "stable" ] ; then \ - echo "switch to stable Tag v${CERT_SERVICE_VERSION}" && \ - git checkout tags/${CERT_SERVICE_VERSION} 2> /dev/null ; \ - elif [ "${BUILD_TYPE}" == "development" ] ; then \ - echo "switch to development Branch" && \ - git checkout development 2> /dev/null ; \ + if [ "${BUILD_TYPE}" == "local" ] ; then \ + echo "use local sources" && \ + mv /ruby-icinga-cert-service /tmp/ && \ + cd ruby-icinga-cert-service ; \ + else \ + git clone https://github.com/bodsch/ruby-icinga-cert-service.git && \ + cd ruby-icinga-cert-service && \ + if [ "${BUILD_TYPE}" == "stable" ] ; then \ + echo "switch to stable Tag v${CERT_SERVICE_VERSION}" && \ + git checkout tags/${CERT_SERVICE_VERSION} 2> /dev/null ; \ + elif [ "${BUILD_TYPE}" == "development" ] ; then \ + echo "switch to development Branch" && \ + git checkout development 2> /dev/null ; \ + fi \ fi && \ + set +x && \ bundle install --quiet && \ gem uninstall --quiet \ io-console bundler && \ cp -ar /tmp/ruby-icinga-cert-service/bin /usr/local/ && \ - cp -ar /tmp/ruby-icinga-cert-service/lib /usr/local/ && \ + cp -ar /tmp/ruby-icinga-cert-service/lib /usr/local/ + +RUN \ apk del --quiet --purge .build-deps && \ rm -rf \ /tmp/* \ diff --git a/build/ruby-icinga-cert-service/Gemfile b/build/ruby-icinga-cert-service/Gemfile new file mode 100644 index 0000000..749664e --- /dev/null +++ b/build/ruby-icinga-cert-service/Gemfile @@ -0,0 +1,22 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in icinga2.gemspec +group :tools do + gem 'rake' + gem 'rake-notes' + gem 'rubocop' + gem 'rubocop-checkstyle_formatter' + gem 'rspec' + gem 'rspec_junit_formatter' +end + +# gem 'rake' +gemspec + +gem 'puma', '~> 3.10' +gem 'sinatra', '~> 2.0' +gem 'sinatra-basic-auth' +gem 'rest-client', '~> 2.0' +# gem 'mini_cache', '~> 1.1' +# gem 'rufus-scheduler', '~> 3.4' +# gem 'tzinfo-data', '~> 1.2017' diff --git a/build/ruby-icinga-cert-service/Gemfile.lock b/build/ruby-icinga-cert-service/Gemfile.lock new file mode 100644 index 0000000..2412e96 --- /dev/null +++ b/build/ruby-icinga-cert-service/Gemfile.lock @@ -0,0 +1,143 @@ +PATH + remote: . + specs: + icinga-cert-service (0.15.11) + json (~> 2.1) + rest-client (~> 2.0) + ruby_dig (~> 0) + +GEM + remote: https://rubygems.org/ + specs: + ast (2.3.0) + builder (3.2.3) + coderay (1.1.1) + colored (1.2) + diff-lcs (1.3) + domain_name (0.5.20170404) + unf (>= 0.0.5, < 1.0.0) + et-orbi (1.0.8) + tzinfo + ffi (1.9.18) + guard (0.10.0) + ffi (>= 0.5.0) + thor (~> 0.14.6) + guard-rspec (0.7.3) + guard (>= 0.10.0) + http-cookie (1.0.3) + domain_name (~> 0.5) + json (2.1.0) + method_source (0.8.2) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mini_cache (1.1.0) + mustermann (1.0.0) + netrc (0.11.0) + parallel (1.11.2) + parser (2.4.0.0) + ast (~> 2.2) + powerpack (0.1.1) + pry (0.10.4) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + pry-nav (0.2.4) + pry (>= 0.9.10, < 0.11.0) + pry-remote (0.1.8) + pry (~> 0.9) + slop (~> 3.0) + puma (3.11.0) + rack (2.0.3) + rack-protection (2.0.0) + rack + rainbow (2.2.2) + rake + rake (12.0.0) + rake-notes (0.2.0) + colored + rake + rest-client (2.0.2) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) + rspec (3.6.0) + rspec-core (~> 3.6.0) + rspec-expectations (~> 3.6.0) + rspec-mocks (~> 3.6.0) + rspec-core (3.6.0) + rspec-support (~> 3.6.0) + rspec-expectations (3.6.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.6.0) + rspec-mocks (3.6.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.6.0) + rspec-nc (0.3.0) + rspec (>= 3) + terminal-notifier (>= 1.4) + rspec-support (3.6.0) + rspec_junit_formatter (0.2.3) + builder (< 4) + rspec-core (>= 2, < 4, != 2.12.0) + rubocop (0.49.1) + parallel (~> 1.10) + parser (>= 2.3.3.1, < 3.0) + powerpack (~> 0.1) + rainbow (>= 1.99.1, < 3.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) + rubocop-checkstyle_formatter (0.4.0) + rubocop (>= 0.35.1) + ruby-progressbar (1.8.1) + ruby_dig (0.0.2) + rufus-scheduler (3.4.2) + et-orbi (~> 1.0) + sinatra (2.0.0) + mustermann (~> 1.0) + rack (~> 2.0) + rack-protection (= 2.0.0) + tilt (~> 2.0) + sinatra-basic-auth (0.1.0) + sinatra + slop (3.6.0) + terminal-notifier (1.8.0) + thor (0.14.6) + thread_safe (0.3.6) + tilt (2.0.7) + tzinfo (1.2.4) + thread_safe (~> 0.1) + tzinfo-data (1.2017.3) + tzinfo (>= 1.0.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.4) + unicode-display_width (1.2.1) + +PLATFORMS + ruby + +DEPENDENCIES + guard (~> 0) + guard-rspec (~> 0) + icinga-cert-service! + mini_cache (~> 1.1) + pry (~> 0) + pry-nav (~> 0) + pry-remote (~> 0) + puma (~> 3.10) + rake + rake-notes + rest-client (~> 2.0) + rspec + rspec-nc (~> 0) + rspec_junit_formatter + rubocop + rubocop-checkstyle_formatter + rufus-scheduler (~> 3.4) + sinatra (~> 2.0) + sinatra-basic-auth + tzinfo-data (~> 1.2017) + +BUNDLED WITH + 1.15.4 diff --git a/build/ruby-icinga-cert-service/LICENSE b/build/ruby-icinga-cert-service/LICENSE new file mode 100644 index 0000000..8000a6f --- /dev/null +++ b/build/ruby-icinga-cert-service/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random + Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/build/ruby-icinga-cert-service/README.md b/build/ruby-icinga-cert-service/README.md new file mode 100644 index 0000000..8e268b1 --- /dev/null +++ b/build/ruby-icinga-cert-service/README.md @@ -0,0 +1,296 @@ +icinga-cert-service +=================== + +The Icinga-Cert-Service is a small service for creating, downloading or signing an Icinga2 certificate. +The service can be used to connect Icinga2 satellites or agents dynamically to an Icinga2 master. + +The Cert service is implemented in ruby and offers a simple REST API. + +# Status +[![Build Status](https://travis-ci.org/bodsch/ruby-icinga-cert-service.svg)][travis] +[![Dependency Status](https://gemnasium.com/badges/github.com/bodsch/ruby-icinga-cert-service.svg)][gemnasium] + +[travis]: https://travis-ci.org/bodsch/ruby-icinga-cert-service +[gemnasium]: https://gemnasium.com/github.com/bodsch/ruby-icinga-cert-service + + +# Start + +To start them run `ruby bin/rest-service.rb` + +The following environment variables can be set: + +- `ICINGA_HOST` (default: `nil`) +- `ICINGA_API_PORT` (default: `5665`) +- `ICINGA_API_USER` (default: `root`) +- `ICINGA_API_PASSWORD` (default: `icinga`) +- `REST_SERVICE_PORT` (default: `8080`) +- `REST_SERVICE_BIND` (default: `0.0.0.0`) +- `BASIC_AUTH_USER` (default: `admin`) +- `BASIC_AUTH_PASS` (default: `admin`) + +The REST-service uses an basic-authentication for the first security step. +The second Step is an configured API user into the Icinga2-Master. +The API user credentials must be set as HTTP-Header vars (see the examples below). + +To overwrite the default configuration for the REST-Service, put a `rest-service.yaml` into `/etc` : + +```yaml +--- +icinga: + server: master-server + api: + port: 5665 + user: root + password: icinga + +rest-service: + port: 8080 + bind: 192.168.10.10 + +basic-auth: + user: ba-user + password: v2rys3cr3t +``` + + +# Who to used it + +## With Icinga2 Version 2.8, we can use the new PKI-Proxy Mode + +You can use `expect` on a *satellite* or *agent* to create an certificate request with the *icinga2 node wizard*. +(A complete `expect` example can be found below) + +```bash +expect /init/node-wizard.expect +``` + +After this, you can use the *cert-service* to sign this request: + +```bash +curl \ + --user ${ICINGA_CERT_SERVICE_BA_USER}:${ICINGA_CERT_SERVICE_BA_PASSWORD} \ + --silent \ + --request GET \ + --header "X-API-USER: ${ICINGA_CERT_SERVICE_API_USER}" \ + --header "X-API-PASSWORD: ${ICINGA_CERT_SERVICE_API_PASSWORD}" \ + --write-out "%{http_code}\n" \ + --output /tmp/sign_${HOSTNAME}.json \ + http://${ICINGA_CERT_SERVICE_SERVER}:${ICINGA_CERT_SERVICE_PORT}/v2/sign/${HOSTNAME} +``` + +## Otherwise, the pre 2.8 Mode works well + +To create a certificate: + +```bash +curl \ + --request GET \ + --user ${ICINGA_CERT_SERVICE_BA_USER}:${ICINGA_CERT_SERVICE_BA_PASSWORD} \ + --silent \ + --header "X-API-USER: ${ICINGA_CERT_SERVICE_API_USER}" \ + --header "X-API-KEY: ${ICINGA_CERT_SERVICE_API_PASSWORD}" \ + --output /tmp/request_${HOSTNAME}.json \ + http://${ICINGA_CERT_SERVICE_SERVER}:${ICINGA_CERT_SERVICE_PORT}/v2/request/${HOSTNAME} +``` + +this creates an output file, that we use to download the certificate. + +## Download the created certificate: + +```bash +checksum=$(jq --raw-output .checksum /tmp/request_${HOSTNAME}.json) + +curl \ + --request GET \ + --user ${ICINGA_CERT_SERVICE_BA_USER}:${ICINGA_CERT_SERVICE_BA_PASSWORD} \ + --silent \ + --header "X-API-USER: ${ICINGA_CERT_SERVICE_API_USER}" \ + --header "X-API-KEY: ${ICINGA_CERT_SERVICE_API_PASSWORD}" \ + --header "X-CHECKSUM: ${checksum}" \ + --output ${WORK_DIR}/pki/${HOSTNAME}/${HOSTNAME}.tgz \ + http://${ICINGA_CERT_SERVICE_SERVER}:${ICINGA_CERT_SERVICE_PORT}/v2/cert/${HOSTNAME} +``` + +## Create the Satellite `Endpoint` + +```bash +cat << EOF > /etc/icinga2/zones.conf + +/* the following line specifies that the client connects to the master and not vice versa */ +object Endpoint "${master_name}" { host = "${ICINGA_MASTER}"; port = "5665" } +object Zone "master" { endpoints = [ "${master_name}" ] } + +object Endpoint NodeName {} +object Zone ZoneName { endpoints = [ NodeName ] ; parent = "master" } + +object Zone "global-templates" { global = true } +object Zone "director-global" { global = true } + +EOF +``` + +## NOTE +The generated certificate has an timeout from 10 minutes between beginning of creation and download. + + +# API + +following API Calls are implemented: + +## Health Check + +The Health Check is important to determine whether the certificate service has started. + +```bash +curl \ + --request GET \ + --silent \ + http://${REST-SERVICE}:8080/v2/health-check +``` + +The health check returns only a string with `healthy` as content. + +## Icinga Version + +Returns the Icinga Version + +```bash +curl \ + --request GET \ + --silent \ + http://${REST-SERVICE}:8080/v2/icinga-version +``` + +The icinga version call returns only a string with the shortend version as content: `2.8` + +## create a certificate request + +Create an Certificate request + +```bash +curl \ + --user ${ICINGA_CERT_SERVICE_BA_USER}:${ICINGA_CERT_SERVICE_BA_PASSWORD} \ + --request GET \ + --header "X-API-USER: cert-service" \ + --header "X-API-KEY: knockknock" \ + http://${REST-SERVICE}:8080/v2/request/${HOST-NAME} +``` + +## download an certificate + +After an certificate request, you can download the created certificate: + +```bash +curl \ + --user ${ICINGA_CERT_SERVICE_BA_USER}:${ICINGA_CERT_SERVICE_BA_PASSWORD} \ + --request GET \ + --header "X-API-USER: cert-service" \ + --header "X-API-KEY: knockknock" \ + --output /tmp/${HOST-NAME}.tgz \ + http://${REST-SERVICE}:8080/v2/cert/${HOST-NAME} +``` + +## validate the satellite CA + +If the CA has been renewed on the master, all satellites or agents will no longer be able to connect to the master. +To be able to detect this possibility, you can create a checksum of the `ca.crt` file and have it checked by the certificats service. + +The following algorithms are supported to create a checksum: +- ´md5` +- ´sha256` +- ´sha384` +- ´sha512` + +```bash +checksum=$(sha256sum ${ICINGA_CERT_DIR}/ca.crt | cut -f 1 -d ' ') + +curl \ + --request GET \ + http://${REST-SERVICE}:8080/v2/validate/${checksum} +``` + +## sign a certificate request + +Version 2.8 of Icinga2 came with a CA proxy. +Here you can use the well-known `node wizard` to create a certificate request on a satellite or agent. +This certificate only has to be confirmed at the Icinga2 Master. + +The certificate files are then replicated to the respective applicant. + +With the following API call you can confirm the certificate without being logged on to the master. + +```bash +curl \ + --user ${ICINGA_CERT_SERVICE_BA_USER}:${ICINGA_CERT_SERVICE_BA_PASSWORD} \ + --request POST \ + --header "X-API-USER: cert-service" \ + --header "X-API-KEY: knockknock" \ + http://${REST-SERVICE}:8080/v2/sign/${HOST-NAME} +``` + +The `node wizard` can also be automated (via `expect`): + +``` + +cat << EOF >> ~/node-wizard.expect + +#!/usr/bin/expect + +# exp_internal 1 + +log_user 1 +set timeout 3 + +spawn icinga2 node wizard + +expect -re "Please specify if this is a satellite/client setup" { + send -- "y\r" +} +expect -re "Please specify the common name " { + send -- "[exec hostname -f]\r" +} +expect -re "Master/Satellite Common Name" { + send -- "$env(ICINGA_MASTER)\r" +} +expect -re "Do you want to establish a connection to the parent node" { + send -- "y\r" +} +expect -re "endpoint host" { + send -- "$env(ICINGA_MASTER)\r" +} +expect -re "endpoint port" { + send -- "5665\r" +} +expect -re "Add more master/satellite endpoints" { + send -- "n\r" +} +expect -re "Is this information correct" { + send -- "y\r" +} +expect -re "Please specify the request ticket generated on your Icinga 2 master" { + send -- "\r" +} +expect -re "Bind Host" { + send -- "\r" +} +expect -re "Bind Port" { + send -- "\r" +} +expect -re "config from parent node" { + send -- "y\r" +} +expect -re "commands from parent node" { + send -- "y\r" +} + +interact + +EOF + + +expect ~/node-wizard.expect 1> /dev/null + +``` + + diff --git a/build/ruby-icinga-cert-service/Rakefile b/build/ruby-icinga-cert-service/Rakefile new file mode 100644 index 0000000..ad2e817 --- /dev/null +++ b/build/ruby-icinga-cert-service/Rakefile @@ -0,0 +1,37 @@ + +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' +require 'bundler/gem_tasks' +require 'rubocop/rake_task' +require 'rake/testtask' + +# Default directory to look in is `/specs` +# Run with `rake spec` +RSpec::Core::RakeTask.new(:spec) do |task| + task.rspec_opts = ['--color'] +end + +desc 'Run all style checks' +task :style => ['style:ruby'] + +desc 'Run all regular tasks' +task :default => :spec + +desc 'Run all tests' +task :test => ['test'] + +namespace :style do + desc 'Run Ruby style checks' + RuboCop::RakeTask.new(:ruby) do |task| + task.patterns = ['**/*.rb'] + # don't abort rake on failure + task.fail_on_error = false + end +end + + +Rake::TestTask.new("test:all") do |t| + t.libs = ["lib", "spec"] + t.warning = true + t.test_files = FileList['spec/**/*_spec.rb'] +end diff --git a/build/ruby-icinga-cert-service/bin/rest-service.rb b/build/ruby-icinga-cert-service/bin/rest-service.rb new file mode 100755 index 0000000..20a83c9 --- /dev/null +++ b/build/ruby-icinga-cert-service/bin/rest-service.rb @@ -0,0 +1,268 @@ +#!/usr/bin/env ruby +# +# 05.10.2016 - Bodo Schulz +# +# +# v2.1.0 + +# ----------------------------------------------------------------------------- + +require 'ruby_dig' if RUBY_VERSION < '2.3' + +require 'sinatra/base' +require 'sinatra/basic_auth' +require 'json' +require 'yaml' + +require_relative '../lib/cert-service' +require_relative '../lib/logging' + +# ----------------------------------------------------------------------------- + +module Sinatra + class CertServiceRest < Base + register Sinatra::BasicAuth + + include Logging + + @icinga_master = ENV.fetch('ICINGA_HOST' , nil) + @icinga_api_port = ENV.fetch('ICINGA_API_PORT' , 5665 ) + @icinga_api_user = ENV.fetch('ICINGA_API_USER' , 'root' ) + @icinga_api_password = ENV.fetch('ICINGA_API_PASSWORD', 'icinga' ) + @rest_service_port = ENV.fetch('REST_SERVICE_PORT' , 8080 ) + @rest_service_bind = ENV.fetch('REST_SERVICE_BIND' , '0.0.0.0' ) + @basic_auth_user = ENV.fetch('BASIC_AUTH_USER' , 'admin') + @basic_auth_pass = ENV.fetch('BASIC_AUTH_PASS' , 'admin') + + configure do + set :environment, :production + + # default configuration + @rest_service_port = 8080 + @rest_service_bind = '0.0.0.0' + + if( File.exist?('/etc/rest-service.yaml') ) + + config = YAML.load_file('/etc/rest-service.yaml') + + @icinga_master = config.dig('icinga', 'server') + @icinga_api_port = config.dig('icinga', 'api', 'port') || 5665 + @icinga_api_user = config.dig('icinga', 'api', 'user') || 5665 + @icinga_api_password = config.dig('icinga', 'api', 'password') || 5665 + @rest_service_port = config.dig('rest-service', 'port') || 8080 + @rest_service_bind = config.dig('rest-service', 'bind') || '0.0.0.0' + @basic_auth_user = config.dig('basic-auth', 'user') || 'admin' + @basic_auth_pass = config.dig('basic-auth', 'password') || 'admin' + else + puts 'no configuration exists, use default settings' + end + end + + set :logging, true + set :app_file, caller_files.first || $PROGRAM_NAME + set :run, proc { $PROGRAM_NAME == app_file } + set :dump_errors, true + set :show_exceptions, true + set :public_folder, '/var/www/' + + set :bind, @rest_service_bind + set :port, @rest_service_port.to_i + + # ----------------------------------------------------------------------------- + + error do + msg = "ERROR\n\nThe cert-rest-service has nasty error - " + env['sinatra.error'] + + msg.message + end + + # ----------------------------------------------------------------------------- + + before do + content_type :json + end + + before '/v2/*/:host' do + request.body.rewind + @request_paylod = request.body.read + end + + # ----------------------------------------------------------------------------- + + # configure Basic Auth + authorize 'API' do |username, password| + username == @basic_auth_user && password == @basic_auth_pass + end + + # ----------------------------------------------------------------------------- + + config = { + icinga: { + server: @icinga_master, + api: { + port: @icinga_api_port, + user: @icinga_api_user, + password: @icinga_api_password, + pki_path: @icinga_api_pki_path, + node_name: @icinga_api_node_name + } + } + } + + ics = IcingaCertService::Client.new(config) + + get '/v2/health-check' do + status 200 + 'healthy' + end + + get '/v2/icinga-version' do + status 200 + result = ics.icinga_version + result + "\n" + end + + # curl \ + # -u "foo:bar" \ + # --request GET \ + # --header "X-API-USER: cert-service" \ + # --header "X-API-KEY: knockknock" \ + # http://$REST-SERVICE:8080/v2/request/$HOST-NAME + # + protect 'API' do + get '/v2/request/:host' do + result = ics.create_certificate(host: params[:host], request: request.env) + result_status = result.dig(:status).to_i + + status result_status + + JSON.pretty_generate(result) + "\n" + end + end + + # curl \ + # -u "foo:bar" \ + # --request POST \ + # http://$REST-SERVICE:8080/v2/ticket/$HOST-NAME + # + protect 'API' do + post '/v2/ticket/:host' do + status 200 + + result = ics.create_ticket(host: params[:host]) + + JSON.pretty_generate(result) + "\n" + end + end + + # curl \ + # -u "foo:bar" \ + # --request GET \ + # http://$REST-SERVICE:8080/v2/validate/$CHECKSUM + # + protect 'API' do + get '/v2/validate/:checksum' do + result = ics.validate_certificate(checksum: params[:checksum]) + result_status = result.dig(:status).to_i + + status result_status + content_type :json + JSON.pretty_generate(result) + "\n" + end + end + + + # curl \ + # -u "foo:bar" \ + # --request GET \ + # --header "X-API-USER: cert-service" \ + # --header "X-API-KEY: knockknock" \ + # --header "X-CHECKSUM: ${checksum}" \ + # --output /tmp/$HOST-NAME.tgz \ + # http://$REST-SERVICE:8080/v2/cert/$HOST-NAME + # + protect 'API' do + get '/v2/cert/:host' do + result = ics.check_certificate( host: params[:host], request: request.env ) + + logger.debug(result) + + result_status = result.dig(:status).to_i + + if result_status == 200 + + path = result.dig(:path) + file_name = result.dig(:file_name) + + status result_status + + send_file(format('%s/%s', path, file_name), filename: file_name, type: 'Application/octet-stream') + else + + status result_status + + JSON.pretty_generate(result) + "\n" + end + end + end + + # curl \ + # -u "foo:bar" \ + # --request GET \ + # --header "X-API-USER: cert-service" \ + # --header "X-API-KEY: knockknock" \ + # --header "X-CHECKSUM: ${checksum}" \ + # --output /tmp/ca.crt \ + # http://$REST-SERVICE:8080/v2/master-ca + # + protect 'API' do + get '/v2/master-ca' do + + path= '/var/lib/icinga2' + file_name = 'ca.crt' + if( File.exist?(format('%s/%s', path, file_name) ) ) + status 200 + send_file(format('%s/%s', path, file_name), filename: file_name, type: 'Application/octet-stream') + else + + status 404 + + JSON.pretty_generate('no ca file found') + "\n" + end + end + end + + # curl \ + # -u "foo:bar" \ + # --request POST \ + # --header "X-API-USER: cert-service" \ + # --header "X-API-KEY: knockknock" \ + # http://$REST-SERVICE:8080/v2/sign/$HOST-NAME + # + protect 'API' do + get '/v2/sign/:host' do + status 200 + + result = ics.sign_certificate(host: params[:host], request: request.env) + + JSON.pretty_generate(result) + "\n" + end + end + + + not_found do + jj = { + 'meta' => { + 'code' => 404, + 'message' => 'Request not found.' + } + } + content_type :json + JSON.pretty_generate(jj) + end + + # ----------------------------------------------------------------------------- + run! if app_file == $PROGRAM_NAME + # ----------------------------------------------------------------------------- + end +end diff --git a/build/ruby-icinga-cert-service/bin/test.rb b/build/ruby-icinga-cert-service/bin/test.rb new file mode 100755 index 0000000..d993308 --- /dev/null +++ b/build/ruby-icinga-cert-service/bin/test.rb @@ -0,0 +1,28 @@ +#!/usr/bin/env ruby +# +# 05.10.2016 - Bodo Schulz +# +# +# v2.1.0 + +# ----------------------------------------------------------------------------- + +require 'ruby_dig' if RUBY_VERSION < '2.3' + +require 'sinatra/base' +require 'sinatra/basic_auth' +require 'json' +require 'yaml' + +require_relative '../lib/cert-service' +require_relative '../lib/logging' + +# ----------------------------------------------------------------------------- + +config = { + icinga_master: 'localhost' +} + +ics = IcingaCertService::Client.new(config) + +# ----------------------------------------------------------------------------- diff --git a/build/ruby-icinga-cert-service/icinga-cert-service.gemspec b/build/ruby-icinga-cert-service/icinga-cert-service.gemspec new file mode 100644 index 0000000..1ebbc6c --- /dev/null +++ b/build/ruby-icinga-cert-service/icinga-cert-service.gemspec @@ -0,0 +1,66 @@ + +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'cert-service/version' + +Gem::Specification.new do |s| + + s.name = 'icinga-cert-service' + s.version = IcingaCertService::VERSION + s.date = '2017-10-19' + s.summary = 'Icinga Certificate Service' + s.description = 'Ruby Class to create an provide a Icinga2 Certificate for Satellites or Agents ' + s.authors = ['Bodo Schulz'] + s.email = 'bodo@boone-schulz.de' + + s.files = Dir[ + 'README.md', + 'LICENSE', + 'lib/**/*', + 'doc/*', + 'examples/*.rb' + ] + + s.homepage = 'https://github.com/bodsch/ruby-icinga-cert-service' + s.license = 'LGPL-2.1+' + + begin + + if( RUBY_VERSION >= '2.0' ) + s.required_ruby_version = '~> 2.0' + elsif( RUBY_VERSION <= '2.1' ) + s.required_ruby_version = '~> 2.1' + elsif( RUBY_VERSION <= '2.2' ) + s.required_ruby_version = '~> 2.2' + elsif( RUBY_VERSION <= '2.3' ) + s.required_ruby_version = '~> 2.3' + end + + if( RUBY_VERSION < '2.3' ) + s.add_dependency('ruby_dig', '~> 0') + end + + if( RUBY_VERSION >= '2.3' ) + s.add_dependency('openssl', '~> 2.0') + end + rescue => e + warn "#{$0}: #{e}" + exit! + end + +# s.required_ruby_version = '>= 2.3' +# s.add_dependency('openssl', '~> 2.0') + + s.add_dependency('rest-client', '~> 2.0') + s.add_dependency('json', '~> 2.1') + + s.add_development_dependency('rspec', '~> 0') + s.add_development_dependency('rspec-nc', '~> 0') + s.add_development_dependency('guard', '~> 0') + s.add_development_dependency('guard-rspec', '~> 0') + s.add_development_dependency('pry', '~> 0') + s.add_development_dependency('pry-remote', '~> 0') + s.add_development_dependency('pry-nav', '~> 0') + +end + diff --git a/build/ruby-icinga-cert-service/init-script/openrc/icinga-cert-service b/build/ruby-icinga-cert-service/init-script/openrc/icinga-cert-service new file mode 100644 index 0000000..f90bf68 --- /dev/null +++ b/build/ruby-icinga-cert-service/init-script/openrc/icinga-cert-service @@ -0,0 +1,59 @@ +#!/sbin/openrc-run +# Copyright 1999-2016 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +: CFGFILE=${CFGFILE:=/etc/conf.d/icinga2-cert-service} + +depend() { + use net + config ${CFGFILE} +} + +get_config() { + x=$1 + test -e ${CFGFILE} || return 1 + . ${CFGFILE} +} + +extra_started_commands='reload' +command=${CERT_SERVICE_BIN:-/usr/local/bin/icinga2-cert-service.rb} +command_args= +pidfile=/var/run/icinga2-cert-service.pid +description="Icinga2 certificate service" + +checkconfig() { + get_config + DAEMON=${CERT_SERVICE_BIN:-/usr/local/bin/icinga2-cert-service.rb} + pidfile="/var/run/icinga2-cert-service.pid" + LOGFILE="/var/log/icinga2-cert-service.log" +} + +start() { + checkconfig || return 1 + + ebegin "Starting icinga2 certificate service" + start-stop-daemon \ + ${DEBUG:+"--verbose"} \ + --start \ + --exec "${DAEMON}" \ + --make-pidfile \ + --pidfile "${pidfile}" \ + --background \ + -- > $LOGFILE 2>&1 + + local retval=$? + if [ $retval -ne 0 ]; then + ewarn "Error starting icinga2 certificate service. '$LOGFILE' for details." + fi + eend $retval +} + +stop() { + ebegin "Stopping icinga2 certificate service" + start-stop-daemon \ + --stop \ + --pidfile "${pidfile}" \ + --retry "SIGTERM/15 SIGKILL/30" \ + --progress + eend $? +} diff --git a/build/ruby-icinga-cert-service/lib/cert-service.rb b/build/ruby-icinga-cert-service/lib/cert-service.rb new file mode 100644 index 0000000..6281de5 --- /dev/null +++ b/build/ruby-icinga-cert-service/lib/cert-service.rb @@ -0,0 +1,317 @@ +# +# +# + +require 'socket' +require 'open3' +require 'fileutils' +require 'rest-client' +# require 'mini_cache' +# require 'rufus-scheduler' + +require_relative 'logging' +require_relative 'util' +require_relative 'cert-service/version' +require_relative 'cert-service/monkey' +require_relative 'cert-service/executor' +require_relative 'cert-service/certificate_handler' +require_relative 'cert-service/endpoint_handler' +require_relative 'cert-service/zone_handler' +require_relative 'cert-service/in-memory-cache' + +# ----------------------------------------------------------------------------- + +# +# +# +module IcingaCertService + # Client Class to create on-the-fly a certificate to connect automaticly as satellite to an icinga2-master + # + # + class Client + + include Logging + include Util::Tar + include IcingaCertService::Executor + include IcingaCertService::CertificateHandler + include IcingaCertService::EndpointHandler + include IcingaCertService::ZoneHandler + include IcingaCertService::InMemoryDataCache + + attr_accessor :icinga_version + + # create a new instance + # + # @param [Hash, #read] params to configure the Client + # @option params [String] :icinga_master The name (FQDN or IP) of the icinga2 master + # + # @example + # IcingaCertService::Client.new( icinga_master: 'icinga2-master.example.com' ) + # + def initialize( settings ) + + raise ArgumentError.new('only Hash are allowed') unless( settings.is_a?(Hash) ) + raise ArgumentError.new('missing settings') if( settings.size.zero? ) + + @icinga_master = settings.dig(:icinga, :server) + @icinga_port = settings.dig(:icinga, :api, :port) || 5665 + @icinga_api_user = settings.dig(:icinga, :api, :user) || 'root' + @icinga_api_password = settings.dig(:icinga, :api, :password) || 'icinga' + + raise ArgumentError.new('missing \'icinga server\'') if( @icinga_master.nil? ) + + raise ArgumentError.new(format('wrong type. \'icinga api port\' must be an Integer, given \'%s\'', @icinga_port.class.to_s)) unless( @icinga_port.is_a?(Integer) ) + raise ArgumentError.new(format('wrong type. \'icinga api user\' must be an String, given \'%s\'' , @icinga_api_user.class.to_s)) unless( @icinga_api_user.is_a?(String) ) + raise ArgumentError.new(format('wrong type. \'icinga api password\' must be an String, given \'%s\'', @icinga_api_password.class.to_s)) unless( @icinga_api_password.is_a?(String) ) + + @tmp_directory = '/tmp/icinga-pki' + + version = IcingaCertService::VERSION + date = '2018-01-18' + detect_version + + logger.info('-----------------------------------------------------------------') + logger.info(format(' certificate service for Icinga2 (%s)', @icinga_version)) + logger.info(format(' Version %s (%s)', version, date)) + logger.info(' Copyright 2017-2018 Bodo Schulz') + logger.info('-----------------------------------------------------------------') + logger.info('') + +# @cache = MiniCache::Store.new + # run internal scheduler to remove old data +# scheduler = Rufus::Scheduler.new +# +# scheduler.every( '30s', :first_in => '30s' ) do +# restarter() +# end + + end + + # + # + # + # + def detect_version + + max_retries = 20 + sleep_between_retries = 8 + retried = 0 + + @icinga_version = 'unknown' + + begin + #response = rest_client.get( headers ) + response = RestClient::Request.execute( + method: :get, + url: format('https://%s:%d/v1/status/IcingaApplication', @icinga_master, @icinga_port ), + timeout: 5, + headers: { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }, + user: @icinga_api_user, + password: @icinga_api_password, + verify_ssl: OpenSSL::SSL::VERIFY_NONE + ) + + response = response.body if(response.is_a?(RestClient::Response)) + response = JSON.parse(response) if(response.is_a?(String)) + results = response.dig('results') if(response.is_a?(Hash)) + results = results.first if(results.is_a?(Array)) + app_data = results.dig('status','icingaapplication','app') + version = app_data.dig('version') if(app_data.is_a?(Hash)) + + if(version.is_a?(String)) + parts = version.match(/^r(?[0-9]+\.{0}\.[0-9]+)(.*)/i) + @icinga_version = parts['v'].to_s.strip if(parts) + end + + rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH => e + sleep( sleep_between_retries ) + retry + rescue RestClient::ExceptionWithResponse => e + + if( retried < max_retries ) + retried += 1 + logger.debug( format( 'connection refused (retry %d / %d)', retried, max_retries ) ) + sleep( sleep_between_retries ) + retry + else + raise format( 'Maximum retries (%d) reached. Giving up ...', max_retries ) + end + end + end + + # function to read API Credentials from icinga2 Configuration + # + # @param [Hash, #read] params + # @option params [String] :api_user the API User, default is 'cert-service' + # + # @example + # read_api_credentials( api_user: 'admin' ) + # + # @return [String, #read] the configured Password or nil + # + def read_api_credentials(params = {}) + + api_user = params.dig(:api_user) || 'cert-service' + + file_name = '/etc/icinga2/conf.d/api-users.conf' + + file = File.open(file_name, 'r') + contents = file.read + password = nil + + regexp_long = / # Match she-bang style C-comment + \/\* # Opening delimiter. + [^*]*\*+ # {normal*} Zero or more non-*, one or more * + (?: # Begin {(special normal*)*} construct. + [^*\/] # {special} a non-*, non-\/ following star. + [^*]*\*+ # More {normal*} + )* # Finish "Unrolling-the-Loop" + \/ # Closing delimiter. + /x + + regex = /\"#{api_user}\"(.*){(.*)password(.*)=(.*)\"(?.+[a-zA-Z0-9])\"(.*)}\n/m + + # remove comments + result = contents.gsub(regexp_long, '') + + # split our string into more parts + result = result.split('object ApiUser') + + # now, iterate over all blocks and get the password + # + result.each do |block| + password = block.scan(regex) + + next unless password.is_a?(Array) && password.count == 1 + + password = password.flatten.first + break + end + + password + end + + # add a host to 'api-users.conf' + # + # https://monitoring-portal.org/index.php?thread/41172-icinga2-api-mit-zertifikaten/&postID=251902#post251902 + # + # @param [Hash, #read] params + # @option params [String] :host + # + # @example + # add_api_user( host: 'icinga2-satellite' ) + # + # @return [Hash, #read] if config already created: + # * :status [Integer] 204 + # * :message [String] Message + # @return nil if successful + # + def add_api_user(params) + + logger.debug("add_api_user(#{params})") + + host = params.dig(:host) + + return { status: 500, message: 'no hostname to create an api user' } if( host.nil? ) + + file_name = '/etc/icinga2/conf.d/api-users.conf' + + return { status: 500, message: format( 'api user not successful configured! file %s missing', file_name ) } unless( File.exist?(file_name) ) + + file = File.open(file_name, 'r') + contents = file.read + + regexp_long = / # Match she-bang style C-comment + \/\* # Opening delimiter. + [^*]*\*+ # {normal*} Zero or more non-*, one or more * + (?: # Begin {(special normal*)*} construct. + [^*\/] # {special} a non-*, non-\/ following star. + [^*]*\*+ # More {normal*} + )* # Finish "Unrolling-the-Loop" + \/ # Closing delimiter. + /x + result = contents.gsub(regexp_long, '') + + scan_api_user = result.scan(/object ApiUser(.*)"(?.+\S)"/).flatten + + return { status: 200, message: format('the configuration for the api user %s already exists', host) } if( scan_api_user.include?(host) == true ) + + logger.debug(format('i miss an configuration for api user %s', host)) + + File.open(file_name, 'a') do |f| + f << "/*\n" + f << " * generated at #{Time.now} with certificate service for Icinga2 #{IcingaCertService::VERSION}\n" + f << " */\n" + f << "object ApiUser \"#{host}\" {\n" + f << " client_cn = \"#{host}\"\n" + f << " permissions = [ \"*\" ]\n" + f << "}\n\n" + end + + return { status: 200, message: format('configuration for api user %s has been created', host) } + end + + # reload the icinga2-master using the api + # + # @param [Hash, #read] params + # + # @option params [String] :request + # * HTTP_X_API_USER + # * HTTP_X_API_PASSWORD + # + def reload_icinga_config(params) + + logger.info( 'restart icinga2 process') + + api_user = params.dig(:request, 'HTTP_X_API_USER') + api_password = params.dig(:request, 'HTTP_X_API_PASSWORD') + + return { status: 500, message: 'missing API Credentials - API_USER' } if( api_user.nil?) + return { status: 500, message: 'missing API Credentials - API_PASSWORD' } if( api_password.nil? ) + + password = read_api_credentials( api_user: api_user ) + + return { status: 500, message: 'wrong API Credentials' } if( password.nil? || api_password != password ) + + options = { user: api_user, password: api_password, verify_ssl: OpenSSL::SSL::VERIFY_NONE } + headers = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' } + url = format('https://%s:5665/v1/actions/restart-process', @icinga_master ) + + rest_client = RestClient::Resource.new( URI.encode( url ), options ) + + begin + + response = rest_client.post( {}.to_json, headers ) + + response = response.body if(response.is_a?(RestClient::Response)) + response = JSON.parse(response) if(response.is_a?(String)) + + logger.debug(JSON.pretty_generate(response)) + + rescue RestClient::ExceptionWithResponse => e + + logger.error("Error: restart-process has failed: '#{e}'") + logger.error(JSON.pretty_generate(params)) + + return { status: 500, message: e } + end + + { status: 200, message: 'service restarted' } + end + + +# def restarter() +# logger.debug( " => restarter" ) +# restart = @cache.get( 'reload' ) +# # logger.debug( "cache: #{restart}" ) +# unless( restart.nil? ) +# host = restart.dig(:host) +# logger.debug( "restart icinga service (#{host})") +# reload_icinga_config(restart) +# +# @cache.unset( 'reload' ) +# end +# end + + end +end diff --git a/build/ruby-icinga-cert-service/lib/cert-service/certificate_handler.rb b/build/ruby-icinga-cert-service/lib/cert-service/certificate_handler.rb new file mode 100644 index 0000000..1d18362 --- /dev/null +++ b/build/ruby-icinga-cert-service/lib/cert-service/certificate_handler.rb @@ -0,0 +1,494 @@ + +require 'open3' + +require_relative 'monkey' + +module IcingaCertService + + module CertificateHandler + + # create a certificate + # + # @param [Hash, #read] params + # @option params [String] :host + # @option params [Hash] :request + # + # @example + # create_certificate( host: 'icinga2-satellite', request: { 'HTTP_X_API_USER => 'admin', HTTP_X_API_PASSWORD' => 'admin' } } ) + # + # @return [Hash, #read] + # * :status [Integer] 200 for successful, or 500 for an error + # * :master_name [String] the Name of the Icinga2-master (need to configure the satellite correctly) + # * :master_ip [String] the IP of the Icinga2-master (need to configure the satellite correctly) + # * :checksum [String] a created Checksum to retrive the certificat archive + # * :timestamp [Integer] a timestamp for the created archive + # * :timeout [Integer] the timeout for the created archive + # * :file_name [String] the archive Name + # * :path [String] the Path who stored the certificate archive + # + def create_certificate( params ) + + host = params.dig(:host) + api_user = params.dig(:request, 'HTTP_X_API_USER') + api_password = params.dig(:request, 'HTTP_X_API_PASSWORD') + + return { status: 500, message: 'no hostname' } if( host.nil? ) + return { status: 500, message: 'missing API Credentials - API_USER' } if( api_user.nil?) + return { status: 500, message: 'missing API Credentials - API_PASSWORD' } if( api_password.nil? ) + + password = read_api_credentials( api_user: api_user ) + + return { status: 500, message: 'wrong API Credentials' } if( password.nil? || api_password != password ) + + if( @icinga_master.nil? ) + begin + server_name = Socket.gethostbyname(Socket.gethostname).first + rescue => e + logger.error(e) + + server_name = @icinga_master + else + server_ip = IPSocket.getaddress(Socket.gethostname) + end + else + server_name = @icinga_master + + begin + server_ip = IPSocket.getaddress(server_name) + rescue => e + logger.error(server_name) + logger.error(e) + + server_ip = '127.0.0.1' + end + end + + pki_base_directory = '/etc/icinga2/pki' + pki_base_directory = '/var/lib/icinga2/certs' if( @icinga_version == '2.8' ) + + return { status: 500, message: 'no PKI directory found. Please configure first the Icinga2 Master!' } if( pki_base_directory.nil? ) + + pki_master_key = format('%s/%s.key', pki_base_directory, server_name) + pki_master_csr = format('%s/%s.csr', pki_base_directory, server_name) + pki_master_crt = format('%s/%s.crt', pki_base_directory, server_name) + pki_master_ca = format('%s/ca.crt', pki_base_directory) + + return { status: 500, message: 'no PKI directory found. Please configure first the Icinga2 Master!' } unless( File.exist?(pki_base_directory) ) + + zone_base_directory = '/etc/icinga2/zone.d' + + FileUtils.mkpath( format('%s/global-templates', zone_base_directory) ) + FileUtils.mkpath( format('%s/%s', zone_base_directory, host) ) + + # + unless File.exist?(format('%s/global-templates/services.conf', zone_base_directory) ) + + if( File.exist?('/etc/icinga2/conf.d/services.conf') ) + FileUtils.mv('/etc/icinga2/conf.d/services.conf', format('%s/global-templates/services.conf', zone_base_directory)) + else + logger.error('missing services.conf under /etc/icinga2/conf.d') + end + end + + logger.debug(format('search PKI files for the Master \'%s\'', server_name)) + + if( !File.exist?(pki_master_key) || !File.exist?(pki_master_csr) || !File.exist?(pki_master_crt) ) + logger.error('missing file') + logger.debug(pki_master_key) + logger.debug(pki_master_csr) + logger.debug(pki_master_crt) + + return { status: 500, message: format('missing PKI for Icinga2 Master \'%s\'', server_name) } + end + + tmp_host_directory = format('%s/%s', @tmp_directory, host) + # uid = File.stat('/etc/icinga2/conf.d').uid + # gid = File.stat('/etc/icinga2/conf.d').gid + + FileUtils.rmdir(tmp_host_directory) if(File.exist?(tmp_host_directory)) + FileUtils.mkpath(tmp_host_directory) unless File.exist?(tmp_host_directory) + FileUtils.chmod_R(0o777, @tmp_directory) if File.exist?(tmp_host_directory) + + return { status: 500, message: 'can\'t create temporary directory' } unless File.exist?(tmp_host_directory) + + salt = Digest::SHA256.hexdigest(host) + + pki_satellite_key = format('%s/%s.key', tmp_host_directory, host) + pki_satellite_csr = format('%s/%s.csr', tmp_host_directory, host) + pki_satellite_crt = format('%s/%s.crt', tmp_host_directory, host) + pki_ticket = '%PKI_TICKET%' + + commands = [] + + # icinga2 pki new-cert --cn $node --csr $node.csr --key $node.key + # icinga2 pki sign-csr --csr $node.csr --cert $node.crt + commands << format('icinga2 pki new-cert --cn %s --key %s --csr %s', host, pki_satellite_key, pki_satellite_csr) + commands << format('icinga2 pki sign-csr --csr %s --cert %s', pki_satellite_csr, pki_satellite_crt) + + if( @icinga_version == '2.7' ) + commands << format('icinga2 pki save-cert --key %s --cert %s --trustedcert %s/trusted-master.crt --host %s', pki_satellite_key, pki_satellite_crt, tmp_host_directory, server_name) + commands << format('icinga2 pki ticket --cn %s --salt %s', server_name, salt) + commands << format('icinga2 pki request --host %s --port 5665 --ticket %s --key %s --cert %s --trustedcert %s/trusted-master.crt --ca %s', server_name, pki_ticket, pki_satellite_key, pki_satellite_crt, tmp_host_directory, pki_master_ca) + end + + pki_ticket = nil + next_command = nil + + commands.each_with_index do |c, index| + + next_command = commands[index + 1] + result = exec_command(cmd: c) + exit_code = result.dig(:code) + exit_message = result.dig(:message) + + logger.debug( format( ' => %s', c ) ) + logger.debug( format( ' - [%s] %s', exit_code, exit_message ) ) + + if( exit_code != true ) + logger.error(exit_message) + logger.error(format(' command \'%s\'', c)) + logger.error(format(' returned with exit-code \'%s\'', exit_code)) + + return { status: 500, message: format('Internal Error: \'%s\' - \'cmd %s\'', exit_message, c) } + end + + if( exit_message =~ %r{/information\//} ) + # logger.debug( 'no ticket' ) + else + pki_ticket = exit_message.strip + next_command = next_command.gsub!('%PKI_TICKET%', pki_ticket) unless( next_command.nil? ) + end + end + + FileUtils.cp( pki_master_ca, format('%s/ca.crt', tmp_host_directory) ) + + # # TODO + # Build Checksum + # Dir[ sprintf( '%s/*', tmp_host_directory ) ].each do |file| + # if( File.directory?( file ) ) + # next + # end + # + # Digest::SHA2.hexdigest( File.read( file ) ) + # end + # + + # create TAR File + io = tar(tmp_host_directory) + # and compress + gz = gzip(io) + + # write to filesystem + + archive_name = format('%s/%s.tgz', @tmp_directory, host) + + begin + file = File.open(archive_name, 'w') + + file.binmode + file.write(gz.read) + rescue IOError => e + # some error occur, dir not writable etc. + logger.error(e) + ensure + file.close unless file.nil? + end + + checksum = Digest::SHA2.hexdigest(File.read(archive_name)) + timestamp = Time.now + timeout = timestamp.add_minutes(10) + + logger.debug(format(' timestamp : %s', timestamp.to_datetime.strftime('%d-%m-%Y %H:%M:%S'))) + logger.debug(format(' timeout : %s', timeout.to_datetime.strftime('%d-%m-%Y %H:%M:%S'))) + + # store datas in-memory + # + save(checksum, timestamp: timestamp, timeout: timeout, host: host) + + # remove the temporary data + # + FileUtils.rm_rf(tmp_host_directory) + + { + status: 200, + master_name: server_name, + master_ip: server_ip, + checksum: checksum, + timestamp: timestamp.to_i, + timeout: timeout.to_i, + file_name: format('%s.tgz', host), + path: @tmp_directory + } + end + + # create a icinga2 Ticket + # (NOT USED YET) + # + def create_ticket( params ) + + host = params.dig(:host) + + return { status: 500, message: 'no hostname to create a ticket' } if( host.nil? ) + + server_name = Socket.gethostbyname(Socket.gethostname).first + server_ip = IPSocket.getaddress(Socket.gethostname) + + # logger.debug(host) + + file_name = '/etc/icinga2/constants.conf' + + file = File.open(file_name, 'r') + contents = file.read + + regexp_long = / # Match she-bang style C-comment + \/\* # Opening delimiter. + [^*]*\*+ # {normal*} Zero or more non-*, one or more * + (?: # Begin {(special normal*)*} construct. + [^*\/] # {special} a non-*, non-\/ following star. + [^*]*\*+ # More {normal*} + )* # Finish "Unrolling-the-Loop" + \/ # Closing delimiter. + /x + result = contents.gsub(regexp_long, '') + +# logger.debug(result) + + ticket_salt = result.scan(/const TicketSalt(.*)=(.*)"(?.+\S)"/).flatten + host_ticket = nil + + if( ticket_salt.to_s != '' ) + logger.debug(format(' ticket Salt : %s', ticket_salt)) + else + o = [('a'..'z'), ('A'..'Z'), (0..9)].map(&:to_a).flatten + string = (0...50).map { o[rand(o.length)] }.join + + ticket_salt = Digest::SHA256.hexdigest(string) + + File.write(file_name, text.gsub(/const TicketSalt = ""/, "const TicketSalt = \"#{ticket_salt}\"")) + end + + commands = [] + commands << format('icinga2 pki ticket --cn %s --salt %s', host, ticket_salt) + + commands.each_with_index do |c, _index| + result = exec_command(cmd: c) + + exit_code = result.dig(:code) + exit_message = result.dig(:message) + + if exit_code != true + logger.error(format('command \'%s\'', c)) + logger.error(format('returned with exit-code %d', exit_code)) + logger.error(exit_message) + + abort 'FAILED !!!' + end + + host_ticket = exit_message +# logger.debug(host_ticket) + end + + timestamp = Time.now + + { + status: 200, + master_name: server_name, + master_ip: server_ip, + ticket: host_ticket, + timestamp: timestamp.to_i + } + end + + # check the certificate Data + # + # @param [Hash, #read] params + # @option params [String] :host + # @option params [Hash] :request + # + # @example + # check_certificate( host: 'icinga2-satellite', request: { 'HTTP_X_CHECKSUM' => '000000000000000000000000000000000000' } ) + # + # @return [Hash, #read] for an error: + # * :status [Integer] 404 or 500 + # * :message [String] Error Message + # + # @return [Hash, #read] for succesfuly: + # * :status [Integer] 200 + # * :file_name [String] Filename + # * :path [String] + # + def check_certificate( params ) + + host = params.dig(:host) + checksum = params.dig(:request, 'HTTP_X_CHECKSUM') + api_user = params.dig(:request, 'HTTP_X_API_USER') + api_password = params.dig(:request, 'HTTP_X_API_PASSWORD') + + return { status: 500, message: 'no valid data to get the certificate' } if( host.nil? || checksum.nil? ) + + file = format('%s/%s.tgz', @tmp_directory, host) + + return { status: 404, message: 'file doesn\'t exits' } unless( File.exist?(file) ) + + in_memory_data = find_by_id(checksum) + generated_timeout = in_memory_data.dig(:timeout) + generated_timeout = File.mtime(file).add_minutes(10) if( generated_timeout.nil? ) + + check_timestamp = Time.now + + return { status: 404, message: 'timed out. please ask for an new cert' } if( check_timestamp.to_i > generated_timeout.to_i ) + + # add params to create the endpoint not in zones.d + # + params[:satellite] = true + + # add API User for this Endpoint + # + # add_api_user(params) + + # add Endpoint (and API User) + # + add_endpoint(params) + + # restart service to activate the new certificate + # + reload_icinga_config(params) + + { status: 200, file_name: format('%s.tgz', host), path: @tmp_directory } + end + + + # validate the CA against a checksum + # + # @param [Hash, #read] params + # @option params [String] :checksum + # + def validate_certificate( params ) + + checksum = params.dig(:checksum) + + return { status: 500, message: 'missing checksum' } if( checksum.nil? ) + + pki_base_directory = '/var/lib/icinga2/ca' + pki_master_ca = format('%s/ca.crt', pki_base_directory) + + return { status: 500, message: 'no PKI directory found. Please configure first the Icinga2 Master!' } unless( File.exist?(pki_base_directory) ) + + if( checksum.be_a_checksum ) + pki_master_ca_checksum = nil + pki_master_ca_checksum = Digest::MD5.hexdigest(File.read(pki_master_ca)) if( checksum.produced_by(:md5) ) + pki_master_ca_checksum = Digest::SHA256.hexdigest(File.read(pki_master_ca)) if( checksum.produced_by(:sha256) ) + pki_master_ca_checksum = Digest::SHA384.hexdigest(File.read(pki_master_ca)) if( checksum.produced_by(:sha384) ) + pki_master_ca_checksum = Digest::SHA512.hexdigest(File.read(pki_master_ca)) if( checksum.produced_by(:sha512) ) + + return { status: 500, message: 'wrong checksum type. only md5, sha256, sha384 and sha512 is supported' } if( pki_master_ca_checksum.nil? ) + return { status: 404, message: 'checksum not match.' } if( checksum != pki_master_ca_checksum ) + return { status: 200 } + end + + { status: 500, message: 'checksum isn\'t a checksum' } + end + + + # sign a icinga2 satellite certificate with the new 2.8 pki feature + # + # @param [Hash, #read] params + # @option params [String] :host + # @option params [Hash] :request + # + # @example + # sign_certificate( host: 'icinga2-satellite', request: { 'HTTP_X_API_USER => 'admin', HTTP_X_API_PASSWORD' => 'admin' } } ) + # + # @return [Hash, #read] for an error: + # * :status [Integer] 404 or 500 + # * :message [String] Error Message + # + # @return [Hash, #read] for succesfuly: + # * :status [Integer] 200 + # * :file_name [String] Filename + # * :path [String] + # + def sign_certificate( params ) + + host = params.dig(:host) + api_user = params.dig(:request, 'HTTP_X_API_USER') + api_password = params.dig(:request, 'HTTP_X_API_PASSWORD') + + return { status: 500, message: 'no hostname' } if( host.nil? ) + return { status: 500, message: 'missing API Credentials - API_USER' } if( api_user.nil?) + return { status: 500, message: 'missing API Credentials - API_PASSWORD' } if( api_password.nil? ) + + password = read_api_credentials( api_user: api_user ) + + return { status: 500, message: 'wrong API Credentials' } if( password.nil? || api_password != password ) + return { status: 500, message: 'wrong Icinga2 Version' } if( @icinga_version != '2.8' ) + + # /etc/icinga2 # icinga2 ca list | grep icinga2-satellite-1.matrix.lan | sort -k2 + # e39c0b4bab4d0d9d5f97f0f54da875f0a60273b4fa3d3ef5d9be0d379e7a058b | Jan 10 04:27:38 2018 GMT | * | CN = icinga2-satellite-1.matrix.lan + # 5520324447b124a26107ded6d5e5b37d73e2cf2074bd2b5e9d8b860939f490df | Jan 10 04:51:38 2018 GMT | | CN = icinga2-satellite-1.matrix.lan + # 6775ea210c7559cf58093dbb151de1aaa3635950f696165eb4beca28487d193c | Jan 10 05:03:36 2018 GMT | | CN = icinga2-satellite-1.matrix.lan + + commands = [] + commands << format('icinga2 ca list | grep %s | sort -k2 | tail -1', host) # sort by date + + commands.each_with_index do |c, index| + + result = exec_command(cmd: c) + exit_code = result.dig(:code) + exit_message = result.dig(:message) + + logger.debug( "icinga2 ca list: #{exit_message}" ) + logger.debug( "exit code: #{exit_code} (#{exit_code.class.to_s})" ) + + return { status: 500, message: 'error to retrive the list of certificates with signing requests' } if( exit_code == false ) + + regex = /^(?.+\S) \|(.*)\|(.*)\| CN = (?.+\S)$/ + parts = exit_message.match(regex) if(exit_message.is_a?(String)) + + logger.debug( "parts: #{parts} (#{parts.class.to_s})" ) + + if( parts ) + ticket = parts['ticket'].to_s.strip + cn = parts['cn'].to_s.strip + + result = exec_command(cmd: format('icinga2 ca sign %s',ticket)) + exit_code = result.dig(:code) + exit_message = result.dig(:message) + + logger.debug(exit_code) + logger.debug(exit_message) + + message = exit_message.gsub('information/cli: ','') + + logger.info(message) + + # create the endpoint and the reference zone + # the endpoint are only after an reload available! + # + add_endpoint(params) + + # logger.debug( format('set reload flag after creating the endpoint (%s)',host) ) + + # set an reload flag + # + # @cache.set( 'reload' , expires_in: 120 ) { MiniCache::Data.new( params ) } + + # reload the icinga configuration + # + #status = reload_icinga_config(params) + #logger.debug(status) + + return { status: 200, message: message } + + else + logger.error(format('i can\'t find a Ticket for host \'%s\'',host)) + logger.error( parts ) + + return { status: 404, message: format('i can\'t find a Ticket for host \'%s\'',host) } + end + end + + { status: 204 } + end + end +end diff --git a/build/ruby-icinga-cert-service/lib/cert-service/configure_icinga.rb b/build/ruby-icinga-cert-service/lib/cert-service/configure_icinga.rb new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/build/ruby-icinga-cert-service/lib/cert-service/configure_icinga.rb @@ -0,0 +1 @@ + diff --git a/build/ruby-icinga-cert-service/lib/cert-service/endpoint_handler.rb b/build/ruby-icinga-cert-service/lib/cert-service/endpoint_handler.rb new file mode 100644 index 0000000..02724f8 --- /dev/null +++ b/build/ruby-icinga-cert-service/lib/cert-service/endpoint_handler.rb @@ -0,0 +1,115 @@ + +module IcingaCertService + # Client Class to create on-the-fly a certificate to connect automaticly as satellite to an icinga2-master + # + # + module EndpointHandler + + # add a Endpoint for distributed monitoring to the icinga2-master configuration + # + # @param [Hash, #read] params + # @option params [String] :host + # + # @example + # add_endpoint( host: 'icinga2-satellite' ) + # + # @return [Hash, #read] if config already created: + # * :status [Integer] 204 + # * :message [String] Message + # + # @return nil if successful + # + def add_endpoint(params) + + host = params.dig(:host) + satellite = params.dig(:satellite) || false + + return { status: 500, message: 'no hostname to create an endpoint' } if host.nil? + + # add the API user + # + add_api_user(params) + + # add the zone object + # + add_zone(host) + + if( satellite ) + file_name = '/etc/icinga2/zones.conf' + else + zone_directory = format('/etc/icinga2/zones.d/%s', host) + file_name = format('%s/%s.conf', zone_directory, host) + + FileUtils.mkpath(zone_directory) unless File.exist?(zone_directory) + end + + if( File.exist?(file_name) ) + + file = File.open(file_name, 'r') + contents = file.read + + regexp_long = / # Match she-bang style C-comment + \/\* # Opening delimiter. + [^*]*\*+ # {normal*} Zero or more non-*, one or more * + (?: # Begin {(special normal*)*} construct. + [^*\/] # {special} a non-*, non-\/ following star. + [^*]*\*+ # More {normal*} + )* # Finish "Unrolling-the-Loop" + \/ # Closing delimiter. + /x + result = contents.gsub(regexp_long, '') + + scan_endpoint = result.scan(/object Endpoint(.*)"(?.+\S)"/).flatten + + return { status: 200, message: format('the configuration for the endpoint %s already exists', host) } if( scan_endpoint.include?(host) == true ) + end + + logger.debug(format('i miss an configuration for endpoint %s', host)) + + File.open(file_name, 'a') do |f| + f << "/*\n" + f << " * generated at #{Time.now} with certificate service for Icinga2 #{IcingaCertService::VERSION}\n" + f << " */\n" + f << "object Endpoint \"#{host}\" {}\n" + end + + { status: 200, message: format('configuration for endpoint %s has been created', host) } + end + + end +end + + + +# object Host "icinga2-satellite-1.matrix.lan" { +# import "generic-host" +# check_command = "hostalive" +# address = "icinga2-satellite-1" +# +# vars.os = "Docker" +# vars.client_endpoint = name +# vars.satellite = true +# +# zone = "icinga2-satellite-1.matrix.lan" +# command_endpoint = "icinga2-satellite-1.matrix.lan" +# } +# +# apply Service "icinga" { +# /* import "generic-service" */ +# +# import "icinga-satellite-service" +# check_command = "icinga" +# +# /* zone = "icinga2-satellite-1.matrix.lan" +# command_endpoint = host.vars.client_endpoint */ +# +# assign where host.vars.satellite +# } +# +# +# zones.d/global-templates/templates_services.conf +# template Service "icinga-satellite-service" { +# import "generic-service" +# command_endpoint = host.vars.remote_endpoint +# zone = host.vars.remote_endpoint +# } diff --git a/build/ruby-icinga-cert-service/lib/cert-service/executor.rb b/build/ruby-icinga-cert-service/lib/cert-service/executor.rb new file mode 100644 index 0000000..e3deb34 --- /dev/null +++ b/build/ruby-icinga-cert-service/lib/cert-service/executor.rb @@ -0,0 +1,32 @@ + +require 'open3' + +module IcingaCertService + + module Executor + + # execute system commands with a Open3.popen2() call + # + # @param [Hash, #read] params + # @option params [String] :cmd + # + # @return [Hash, #read] + # * :exit [Integer] Exit-Code + # * :message [String] Message + def exec_command( params ) + + cmd = params.dig(:cmd) + + return { code: 1, message: 'no command found' } if( cmd.nil? ) + + result = {} + + Open3.popen2( cmd ) do |_stdin, stdout_err, wait_thr| + return_value = wait_thr.value + result = { code: return_value.success?, message: stdout_err.gets } + end + + result + end + end +end diff --git a/build/ruby-icinga-cert-service/lib/cert-service/in-memory-cache.rb b/build/ruby-icinga-cert-service/lib/cert-service/in-memory-cache.rb new file mode 100644 index 0000000..f2002bb --- /dev/null +++ b/build/ruby-icinga-cert-service/lib/cert-service/in-memory-cache.rb @@ -0,0 +1,41 @@ + +module IcingaCertService + + # small in-memory Cache + # + module InMemoryDataCache + # create a new Instance + def initialize + @storage = {} + end + + # save data + # + # @param [String, #read] id + # @param [misc, #read] data + # + def save(id, data) + @storage ||= {} + @storage[id] ||= {} + @storage[id] = data + end + + # get data + # + # @param [String, #read] + # + def find_by_id(id) + if( !@storage.nil? ) + @storage.dig(id) || {} + else + {} + end + end + + # get all data + # + def entries + @storage + end + end +end diff --git a/build/ruby-icinga-cert-service/lib/cert-service/monkey.rb b/build/ruby-icinga-cert-service/lib/cert-service/monkey.rb new file mode 100644 index 0000000..7bdb4cd --- /dev/null +++ b/build/ruby-icinga-cert-service/lib/cert-service/monkey.rb @@ -0,0 +1,39 @@ + +# check if is a checksum +# +class Object + + REGEX = /\A[0-9a-f]{32,128}\z/i + CHARS = { + md2: 32, + md4: 32, + md5: 32, + sha1: 40, + sha224: 56, + sha256: 64, + sha384: 96, + sha512: 128 + } + + def be_a_checksum + !!(self =~ REGEX) + end + + def produced_by( name ) + function = name.to_s.downcase.to_sym + + raise ArgumentError, "unknown algorithm given to be_a_checksum.produced_by: #{function}" unless CHARS.include?(function) + + return true if( size == CHARS[function] ) + false + end +end + +# add minutes +# +class Time + def add_minutes(m) + self + (60 * m) + end +end + diff --git a/build/ruby-icinga-cert-service/lib/cert-service/version.rb b/build/ruby-icinga-cert-service/lib/cert-service/version.rb new file mode 100644 index 0000000..ac490b6 --- /dev/null +++ b/build/ruby-icinga-cert-service/lib/cert-service/version.rb @@ -0,0 +1,6 @@ + +# frozen_string_literal: true + +module IcingaCertService + VERSION = '0.15.13'.freeze +end diff --git a/build/ruby-icinga-cert-service/lib/cert-service/zone_handler.rb b/build/ruby-icinga-cert-service/lib/cert-service/zone_handler.rb new file mode 100644 index 0000000..0190291 --- /dev/null +++ b/build/ruby-icinga-cert-service/lib/cert-service/zone_handler.rb @@ -0,0 +1,63 @@ + +module IcingaCertService + # Client Class to create on-the-fly a certificate to connect automaticly as satellite to an icinga2-master + # + # + module ZoneHandler + + # add a satellite zone to 'zones.conf' + # + # @param [String] zone + # + # @example + # add_zone('icinga2-satellite') + # + # @return [Hash, #read] if config already created: + # * :status [Integer] 200 + # * :message [String] Message + # + # @return nil if successful + # + def add_zone(zone) + + return { status: 500, message: 'no zone defined' } if zone.nil? + + zone_file = '/etc/icinga2/zones.conf' + + if(File.exist?(zone_file)) + + file = File.open(zone_file, 'r') + contents = file.read + + regexp_long = / # Match she-bang style C-comment + \/\* # Opening delimiter. + [^*]*\*+ # {normal*} Zero or more non-*, one or more * + (?: # Begin {(special normal*)*} construct. + [^*\/] # {special} a non-*, non-\/ following star. + [^*]*\*+ # More {normal*} + )* # Finish "Unrolling-the-Loop" + \/ # Closing delimiter. + /x + result = contents.gsub(regexp_long, '') + scan_zone = result.scan(/object Zone(.*)"(?.+\S)"/).flatten + + return { status: 200, message: format('the configuration for the zone %s already exists', zone) } if( scan_zone.include?(zone) == true ) + end + + logger.debug(format('i miss an configuration for zone %s', zone)) + + File.open(zone_file, 'a') do |f| + f << "/*\n" + f << " * generated at #{Time.now} with certificate service for Icinga2 #{IcingaCertService::VERSION}\n" + f << " */\n" + f << "object Zone \"#{zone}\" {\n" + f << " parent = \"#{@icinga_master}\"\n" + f << " endpoints = [ \"#{zone}\" ]\n" + f << "}\n\n" + end + + { status: 200, message: format('configuration for zone %s has been created', zone) } + end + + end +end diff --git a/build/ruby-icinga-cert-service/lib/logging.rb b/build/ruby-icinga-cert-service/lib/logging.rb new file mode 100644 index 0000000..efa35f3 --- /dev/null +++ b/build/ruby-icinga-cert-service/lib/logging.rb @@ -0,0 +1,37 @@ + +require 'logger' + +# ------------------------------------------------------------------------------------------------- + +module Logging + + def logger + @logger ||= Logging.logger_for( self.class.name ) + end + + # Use a hash class-ivar to cache a unique Logger per class: + @loggers = {} + + class << self + + def logger_for( classname ) + @loggers[classname] ||= configure_logger_for( classname ) + end + + def configure_logger_for( classname ) + + $stdout.sync = true + logger = Logger.new($stdout) +# logger.progname = classname + logger.level = Logger::DEBUG + logger.datetime_format = '%Y-%m-%d %H:%M:%S::%3N' + logger.formatter = proc do |severity, datetime, progname, msg| + "[#{datetime.strftime( logger.datetime_format )}] #{severity.ljust(5)} : #{progname} #{msg}\n" + end + + logger + end + end +end + +# ------------------------------------------------------------------------------------------------- diff --git a/build/ruby-icinga-cert-service/lib/logging.rb~ b/build/ruby-icinga-cert-service/lib/logging.rb~ new file mode 100644 index 0000000..c84072b --- /dev/null +++ b/build/ruby-icinga-cert-service/lib/logging.rb~ @@ -0,0 +1,41 @@ +#!/usr/bin/ruby + +require 'logger' + +# ------------------------------------------------------------------------------------------------- + +module Logging + + def logger + @logger ||= Logging.logger_for( self.class.name ) + end + + # Use a hash class-ivar to cache a unique Logger per class: + @loggers = {} + + class << self + def logger_for( classname ) + @loggers[classname] ||= configure_logger_for( classname ) + end + + def configure_logger_for( classname ) + + logFile = '/var/log/cert-service.log' + file = File.open( logFile, File::WRONLY | File::APPEND | File::CREAT ) + file.sync = true + logger = Logger.new( file, 'weekly', 1024000 ) + +# logger = Logger.new(STDOUT) + logger.progname = classname + logger.level = Logger::DEBUG + logger.datetime_format = "%Y-%m-%d %H:%M:%S::%3N" + logger.formatter = proc do |severity, datetime, progname, msg| + "[#{datetime.strftime( logger.datetime_format )}] #{severity.ljust(5)} : #{progname} - #{msg}\n" + end + + logger + end + end +end + +# ------------------------------------------------------------------------------------------------- diff --git a/build/ruby-icinga-cert-service/lib/util.rb b/build/ruby-icinga-cert-service/lib/util.rb new file mode 100644 index 0000000..d5259dd --- /dev/null +++ b/build/ruby-icinga-cert-service/lib/util.rb @@ -0,0 +1,90 @@ + +require 'rubygems' +require 'rubygems/package' + +require 'zlib' +require 'fileutils' + +module Util + module Tar + # Creates a tar file in memory recursively + # from the given path. + # + # Returns a StringIO whose underlying String + # is the contents of the tar file. + def tar(path) + tarfile = StringIO.new('') + Gem::Package::TarWriter.new(tarfile) do |tar| + Dir[File.join(path, '**/*')].each do |file| + mode = File.stat(file).mode + relative_file = file.sub /^#{Regexp.escape path}\/?/, '' + + if File.directory?(file) + tar.mkdir relative_file, mode + else + tar.add_file relative_file, mode do |tf| + File.open(file, 'rb') { |f| tf.write f.read } + end + end + end + end + + tarfile.rewind + tarfile + end + + # gzips the underlying string in the given StringIO, + # returning a new StringIO representing the + # compressed file. + def gzip(tarfile) + gz = StringIO.new('') + z = Zlib::GzipWriter.new(gz) + z.write tarfile.string + z.close # this is necessary! + + # z was closed to write the gzip footer, so + # now we need a new StringIO + StringIO.new gz.string + end + + # un-gzips the given IO, returning the + # decompressed version as a StringIO + def ungzip(tarfile) + z = Zlib::GzipReader.new(tarfile) + unzipped = StringIO.new(z.read) + z.close + unzipped + end + + # untars the given IO into the specified + # directory + def untar(io, destination) + Gem::Package::TarReader.new io do |tar| + tar.each do |tarfile| + destination_file = File.join destination, tarfile.full_name + + if tarfile.directory? + FileUtils.mkdir_p destination_file + else + destination_directory = File.dirname(destination_file) + FileUtils.mkdir_p destination_directory unless File.directory?(destination_directory) + File.open destination_file, 'wb' do |f| + f.print tarfile.read + end + end + end + end + end + end +end + +### Usage Example: ### +# +# include Util::Tar +# +# io = tar("./Desktop") # io is a TAR of files +# gz = gzip(io) # gz is a TGZ +# +# io = ungzip(gz) # io is a TAR +# untar(io, "./untarred") # files are untarred +# diff --git a/docker-compose_example.yml b/docker-compose_example.yml index 5e1f193..23ba90c 100644 --- a/docker-compose_example.yml +++ b/docker-compose_example.yml @@ -56,6 +56,7 @@ services: - MYSQL_ROOT_PASS=vYUQ14SGVrJRi69PsujC - IDO_PASSWORD=qUVuLTk9oEDUV0A # environment variables for the certificates service + - ICINGA_HOST=icinga2-master.matrix.lan - ICINGA_MASTER=icinga2-master.matrix.lan - BASIC_AUTH_USER=foofoo - BASIC_AUTH_PASS=barbar diff --git a/rootfs/init/run.sh b/rootfs/init/run.sh index 3167126..0c995a7 100755 --- a/rootfs/init/run.sh +++ b/rootfs/init/run.sh @@ -95,6 +95,8 @@ run() { # backup the generated zones # nohup /init/runtime/inotify.sh > /dev/stdout 2>&1 & + + env | grep ICINGA | sort nohup /usr/local/bin/rest-service.rb > /dev/stdout 2>&1 & else : diff --git a/rootfs/init/runtime/inotify.sh b/rootfs/init/runtime/inotify.sh index 0501ecd..10d9d56 100755 --- a/rootfs/init/runtime/inotify.sh +++ b/rootfs/init/runtime/inotify.sh @@ -26,7 +26,7 @@ inotifywait \ while read path action file do - if ( [[ -z "${file}" ]] || [[ ! ${file} =~ ^zones* ]] ) + if ( [[ -z "${file}" ]] || [[ ! ${file} =~ ^zones* ]] || [[ "${file}" != "api-users.conf" ]] ) then continue fi @@ -61,8 +61,10 @@ inotifywait \ --archive \ --recursive \ --delete \ + --verbose \ --include="zones.d/***" \ --include="zones.*" \ + --include="conf.d/api-users.conf" \ --exclude='*' \ ${monitored_directory}/* ${backup_directory}/ fi From b7045f3605abc539bcacc094b24270bd9fc8e983 Mon Sep 17 00:00:00 2001 From: Bodo Schulz Date: Tue, 16 Jan 2018 07:01:24 +0100 Subject: [PATCH 2/5] save api-users.conf too --- rootfs/init/icinga_types/master.sh | 2 +- rootfs/init/runtime/inotify.sh | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/rootfs/init/icinga_types/master.sh b/rootfs/init/icinga_types/master.sh index 26011ae..2c4feca 100644 --- a/rootfs/init/icinga_types/master.sh +++ b/rootfs/init/icinga_types/master.sh @@ -18,10 +18,10 @@ restore_old_zone_config() { --delete \ --include="zones.d/***" \ --include="zones.*" \ + # TODO api-users.conf --exclude='*' \ ${ICINGA_LIB_DIR}/backup/* /etc/icinga2/ fi - } diff --git a/rootfs/init/runtime/inotify.sh b/rootfs/init/runtime/inotify.sh index 10d9d56..e0980a0 100755 --- a/rootfs/init/runtime/inotify.sh +++ b/rootfs/init/runtime/inotify.sh @@ -26,7 +26,7 @@ inotifywait \ while read path action file do - if ( [[ -z "${file}" ]] || [[ ! ${file} =~ ^zones* ]] || [[ "${file}" != "api-users.conf" ]] ) + if ( [[ -z "${file}" ]] || [[ ! ${file} =~ ^zones* ]] && [[ "${file}" != "api-users.conf" ]] ) then continue fi @@ -64,6 +64,7 @@ inotifywait \ --verbose \ --include="zones.d/***" \ --include="zones.*" \ + --include="conf.d" \ --include="conf.d/api-users.conf" \ --exclude='*' \ ${monitored_directory}/* ${backup_directory}/ From 0b7dfe6b2bce11ea624ac215cda33647b0737000 Mon Sep 17 00:00:00 2001 From: Bodo Schulz Date: Tue, 16 Jan 2018 16:02:22 +0100 Subject: [PATCH 3/5] update for the next icinga-cert-service - backup api-user.conf - reduce functionality of inotify.sh --- Dockerfile | 16 +--- Dockerfile.DEV | 96 +++++++++++++++++++ .../lib/cert-service.rb | 4 +- .../lib/cert-service/backup.rb | 37 +++++++ .../lib/cert-service/certificate_handler.rb | 3 +- .../lib/cert-service/endpoint_handler.rb | 2 + .../lib/cert-service/version.rb | 2 +- build/ruby-icinga-cert-service/lib/logging.rb | 2 +- rootfs/etc/icinga2/conf.d/api-users.conf | 8 -- rootfs/init/api_user.sh | 35 ++++--- rootfs/init/common.sh | 35 +++++++ rootfs/init/icinga_types/master.sh | 22 ++--- rootfs/init/run.sh | 4 +- rootfs/init/runtime/inotify.sh | 41 ++++---- rootfs/init/wait_for/cert_service.sh | 10 +- 15 files changed, 237 insertions(+), 80 deletions(-) create mode 100644 Dockerfile.DEV create mode 100644 build/ruby-icinga-cert-service/lib/cert-service/backup.rb diff --git a/Dockerfile b/Dockerfile index 6389ac6..f5bd1ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,8 +5,8 @@ ENV \ TERM=xterm \ TZ='Europe/Berlin' \ BUILD_DATE="2018-01-18" \ - BUILD_TYPE="local" \ - CERT_SERVICE_VERSION="0.15.12" \ + BUILD_TYPE="stable" \ + CERT_SERVICE_VERSION="0.16.3" \ ICINGA_VERSION="2.8.0-r0" EXPOSE 5665 8080 @@ -27,8 +27,6 @@ LABEL \ # --------------------------------------------------------------------------------------- -ADD build/ / - RUN \ apk update --quiet --no-cache && \ apk upgrade --quiet --no-cache && \ @@ -45,10 +43,7 @@ RUN \ chmod u+s /bin/busybox && \ echo 'gem: --no-document' >> /etc/gemrc && \ gem install --quiet --no-rdoc --no-ri \ - io-console bundler - -RUN \ - set -x && \ + io-console bundler && \ cd /tmp && \ if [ "${BUILD_TYPE}" == "local" ] ; then \ echo "use local sources" && \ @@ -65,14 +60,11 @@ RUN \ git checkout development 2> /dev/null ; \ fi \ fi && \ - set +x && \ bundle install --quiet && \ gem uninstall --quiet \ io-console bundler && \ cp -ar /tmp/ruby-icinga-cert-service/bin /usr/local/ && \ - cp -ar /tmp/ruby-icinga-cert-service/lib /usr/local/ - -RUN \ + cp -ar /tmp/ruby-icinga-cert-service/lib /usr/local/ && \ apk del --quiet --purge .build-deps && \ rm -rf \ /tmp/* \ diff --git a/Dockerfile.DEV b/Dockerfile.DEV new file mode 100644 index 0000000..e7a385f --- /dev/null +++ b/Dockerfile.DEV @@ -0,0 +1,96 @@ + +FROM alpine:3.7 + +ENV \ + TERM=xterm \ + TZ='Europe/Berlin' \ + BUILD_DATE="2018-01-18" \ + BUILD_TYPE="stable" \ + CERT_SERVICE_VERSION="0.16.3" \ + ICINGA_VERSION="2.8.0-r0" + +EXPOSE 5665 8080 + +LABEL \ + version="1801" \ + maintainer="Bodo Schulz " \ + org.label-schema.build-date=${BUILD_DATE} \ + org.label-schema.name="Icinga2 Docker Image" \ + org.label-schema.description="Inofficial Icinga2 Docker Image" \ + org.label-schema.url="https://www.icinga.org/" \ + org.label-schema.vcs-url="https://github.com/bodsch/docker-icinga2" \ + org.label-schema.vendor="Bodo Schulz" \ + org.label-schema.version=${ICINGA_VERSION} \ + org.label-schema.schema-version="1.0" \ + com.microscaling.docker.dockerfile="/Dockerfile" \ + com.microscaling.license="GNU General Public License v3.0" + +# --------------------------------------------------------------------------------------- + +ADD build/ / + +RUN \ + apk update --quiet --no-cache && \ + apk upgrade --quiet --no-cache && \ + apk add --quiet --no-cache --virtual .build-deps \ + libffi-dev g++ make git openssl-dev ruby-dev && \ + apk add --quiet --no-cache \ + bash bind-tools curl expect fping inotify-tools icinga2 jq mailx monitoring-plugins mariadb-client netcat-openbsd nmap nrpe-plugin openssl pwgen ruby rsync ssmtp tzdata unzip && \ + cp /etc/icinga2/conf.d.example/* /etc/icinga2/conf.d/ && \ + ln -s /usr/lib/nagios/plugins/* /usr/lib/monitoring-plugins/ && \ + /usr/sbin/icinga2 feature enable command checker mainlog notification && \ + mkdir -p /etc/icinga2/objects.d && \ + mkdir -p /run/icinga2/cmd && \ + cp /etc/icinga2/zones.conf /etc/icinga2/zones.conf-distributed && \ + chmod u+s /bin/busybox && \ + echo 'gem: --no-document' >> /etc/gemrc && \ + gem install --quiet --no-rdoc --no-ri \ + io-console bundler + +RUN \ + cd /tmp && \ + if [ "${BUILD_TYPE}" == "local" ] ; then \ + echo "use local sources" && \ + mv /ruby-icinga-cert-service /tmp/ && \ + cd ruby-icinga-cert-service ; \ + else \ + git clone https://github.com/bodsch/ruby-icinga-cert-service.git && \ + cd ruby-icinga-cert-service && \ + if [ "${BUILD_TYPE}" == "stable" ] ; then \ + echo "switch to stable Tag v${CERT_SERVICE_VERSION}" && \ + git checkout tags/${CERT_SERVICE_VERSION} 2> /dev/null ; \ + elif [ "${BUILD_TYPE}" == "development" ] ; then \ + echo "switch to development Branch" && \ + git checkout development 2> /dev/null ; \ + fi \ + fi && \ + bundle install --quiet && \ + gem uninstall --quiet \ + io-console bundler && \ + cp -ar /tmp/ruby-icinga-cert-service/bin /usr/local/ && \ + cp -ar /tmp/ruby-icinga-cert-service/lib /usr/local/ + +RUN \ + apk del --quiet --purge .build-deps && \ + rm -rf \ + /tmp/* \ + /var/cache/apk/* \ + /root/.gem \ + /root/.bundle + +COPY rootfs/ / + +WORKDIR "/etc/icinga2" + +VOLUME [ "/etc/icinga2", "/var/lib/icinga2" ] + +HEALTHCHECK \ + --interval=5s \ + --timeout=2s \ + --retries=12 \ + --start-period=10s \ + CMD ps ax | grep -v grep | grep -c "/usr/lib/icinga2/sbin/icinga2" || exit 1 + +CMD [ "/init/run.sh" ] + +# --------------------------------------------------------------------------------------- diff --git a/build/ruby-icinga-cert-service/lib/cert-service.rb b/build/ruby-icinga-cert-service/lib/cert-service.rb index 6281de5..2ec2b55 100644 --- a/build/ruby-icinga-cert-service/lib/cert-service.rb +++ b/build/ruby-icinga-cert-service/lib/cert-service.rb @@ -18,6 +18,7 @@ require_relative 'cert-service/endpoint_handler' require_relative 'cert-service/zone_handler' require_relative 'cert-service/in-memory-cache' +require_relative 'cert-service/backup' # ----------------------------------------------------------------------------- @@ -37,6 +38,7 @@ class Client include IcingaCertService::EndpointHandler include IcingaCertService::ZoneHandler include IcingaCertService::InMemoryDataCache + include IcingaCertService::Backup attr_accessor :icinga_version @@ -208,8 +210,6 @@ def read_api_credentials(params = {}) # def add_api_user(params) - logger.debug("add_api_user(#{params})") - host = params.dig(:host) return { status: 500, message: 'no hostname to create an api user' } if( host.nil? ) diff --git a/build/ruby-icinga-cert-service/lib/cert-service/backup.rb b/build/ruby-icinga-cert-service/lib/cert-service/backup.rb new file mode 100644 index 0000000..fa6123b --- /dev/null +++ b/build/ruby-icinga-cert-service/lib/cert-service/backup.rb @@ -0,0 +1,37 @@ + +module IcingaCertService + # + # + # + module Backup + + # create Backup of generated files + # + def create_backup + + source_directory = '/etc/icinga2' + backup_directory = '/var/lib/icinga2/backup' + + FileUtils.mkpath(backup_directory) unless File.exist?(backup_directory) + + white_list = %w(zones.d zones.conf conf.d/api-users.conf) + + white_list.each do |p| + + source_file = format( '%s/%s', source_directory, p ) + + destination_directory = File.dirname( source_file ) + destination_directory.gsub!( source_directory, backup_directory ) + + FileUtils.mkpath(destination_directory) unless File.exist?(destination_directory) + + if( File.directory?(source_file) ) + FileUtils.cp_r(source_file, "#{backup_directory}/", :noop => false, :verbose => false ) + else + FileUtils.cp_r(source_file, "#{backup_directory}/#{p}", :noop => false, :verbose => false ) + end + end + + end + end +end diff --git a/build/ruby-icinga-cert-service/lib/cert-service/certificate_handler.rb b/build/ruby-icinga-cert-service/lib/cert-service/certificate_handler.rb index 1d18362..745c56c 100644 --- a/build/ruby-icinga-cert-service/lib/cert-service/certificate_handler.rb +++ b/build/ruby-icinga-cert-service/lib/cert-service/certificate_handler.rb @@ -347,12 +347,13 @@ def check_certificate( params ) # add_api_user(params) # add Endpoint (and API User) + # and create a backup of the generated files # add_endpoint(params) # restart service to activate the new certificate # - reload_icinga_config(params) + # reload_icinga_config(params) { status: 200, file_name: format('%s.tgz', host), path: @tmp_directory } end diff --git a/build/ruby-icinga-cert-service/lib/cert-service/endpoint_handler.rb b/build/ruby-icinga-cert-service/lib/cert-service/endpoint_handler.rb index 02724f8..ae0e002 100644 --- a/build/ruby-icinga-cert-service/lib/cert-service/endpoint_handler.rb +++ b/build/ruby-icinga-cert-service/lib/cert-service/endpoint_handler.rb @@ -73,6 +73,8 @@ def add_endpoint(params) f << "object Endpoint \"#{host}\" {}\n" end + create_backup + { status: 200, message: format('configuration for endpoint %s has been created', host) } end diff --git a/build/ruby-icinga-cert-service/lib/cert-service/version.rb b/build/ruby-icinga-cert-service/lib/cert-service/version.rb index ac490b6..3b77c34 100644 --- a/build/ruby-icinga-cert-service/lib/cert-service/version.rb +++ b/build/ruby-icinga-cert-service/lib/cert-service/version.rb @@ -2,5 +2,5 @@ # frozen_string_literal: true module IcingaCertService - VERSION = '0.15.13'.freeze + VERSION = '0.16.3'.freeze end diff --git a/build/ruby-icinga-cert-service/lib/logging.rb b/build/ruby-icinga-cert-service/lib/logging.rb index efa35f3..1b66802 100644 --- a/build/ruby-icinga-cert-service/lib/logging.rb +++ b/build/ruby-icinga-cert-service/lib/logging.rb @@ -23,7 +23,7 @@ def configure_logger_for( classname ) $stdout.sync = true logger = Logger.new($stdout) # logger.progname = classname - logger.level = Logger::DEBUG + logger.level = Logger::INFO logger.datetime_format = '%Y-%m-%d %H:%M:%S::%3N' logger.formatter = proc do |severity, datetime, progname, msg| "[#{datetime.strftime( logger.datetime_format )}] #{severity.ljust(5)} : #{progname} #{msg}\n" diff --git a/rootfs/etc/icinga2/conf.d/api-users.conf b/rootfs/etc/icinga2/conf.d/api-users.conf index 4da6e5b..d44d1dc 100644 --- a/rootfs/etc/icinga2/conf.d/api-users.conf +++ b/rootfs/etc/icinga2/conf.d/api-users.conf @@ -4,11 +4,3 @@ object ApiUser "root" { client_cn = NodeName permissions = [ "*" ] } - -/* -object ApiUser "dashing" { - password = "icinga2ondashingr0xx" - client_cn = NodeName - permissions = [ "*" ] -} -*/ diff --git a/rootfs/init/api_user.sh b/rootfs/init/api_user.sh index 935d3fa..b3986d1 100644 --- a/rootfs/init/api_user.sh +++ b/rootfs/init/api_user.sh @@ -15,24 +15,28 @@ create_api_user() { if [[ ! -z "${api_users}" ]] then - log_info "create configuration for API users ..." - # DESTROY the old entrys - # - echo "" > ${api_file} + if [[ $(cat ${api_file} | wc -l) -eq 6 ]] + then + log_info "create configuration for API users ..." - for u in ${api_users} - do - user=$(echo "${u}" | cut -d: -f1) - pass=$(echo "${u}" | cut -d: -f2) + # the initial configuration + # make it blank and create our default users + # + echo "" > ${api_file} - [[ -z ${pass} ]] && pass=${user} + for u in ${api_users} + do + user=$(echo "${u}" | cut -d: -f1) + pass=$(echo "${u}" | cut -d: -f2) - log_info " - '${user}'" + [[ -z ${pass} ]] && pass=${user} - if [[ $(grep -c "object ApiUser \"${user}\"" ${api_file}) -eq 0 ]] - then - cat << EOF >> ${api_file} + if [[ $(grep -c "object ApiUser \"${user}\"" ${api_file}) -eq 0 ]] + then + log_info " add user '${user}'" + + cat << EOF >> ${api_file} object ApiUser "${user}" { password = "${pass}" @@ -41,8 +45,9 @@ object ApiUser "${user}" { } EOF - fi - done + fi + done + fi fi } diff --git a/rootfs/init/common.sh b/rootfs/init/common.sh index 36c4d27..0c134fd 100644 --- a/rootfs/init/common.sh +++ b/rootfs/init/common.sh @@ -4,6 +4,7 @@ DEMO_DATA=${DEMO_DATA:-'false'} USER= GROUP= ICINGA_MASTER=${ICINGA_MASTER:-''} +ICINGA_HOST=${ICINGA_HOST:-${ICINGA_MASTER}} # prepare the system and icinga to run in the docker environment # @@ -155,3 +156,37 @@ curl_opts() { echo ${opts} } + + +validate_cert-service_environment() { + + ICINGA_CERT_SERVICE_BA_USER=${ICINGA_CERT_SERVICE_BA_USER:-"admin"} + ICINGA_CERT_SERVICE_BA_PASSWORD=${ICINGA_CERT_SERVICE_BA_PASSWORD:-"admin"} + ICINGA_CERT_SERVICE_API_USER=${ICINGA_CERT_SERVICE_API_USER:-""} + ICINGA_CERT_SERVICE_API_PASSWORD=${ICINGA_CERT_SERVICE_API_PASSWORD:-""} + ICINGA_CERT_SERVICE_SERVER=${ICINGA_CERT_SERVICE_SERVER:-"localhost"} + ICINGA_CERT_SERVICE_PORT=${ICINGA_CERT_SERVICE_PORT:-"80"} + ICINGA_CERT_SERVICE_PATH=${ICINGA_CERT_SERVICE_PATH:-"/"} + ICINGA_CERT_SERVICE=false + + # use the new Cert Service to create and get a valide certificat for distributed icinga services + # + if ( + [[ ! -z ${ICINGA_CERT_SERVICE_BA_USER} ]] && + [[ ! -z ${ICINGA_CERT_SERVICE_BA_PASSWORD} ]] && + [[ ! -z ${ICINGA_CERT_SERVICE_API_USER} ]] && + [[ ! -z ${ICINGA_CERT_SERVICE_API_PASSWORD} ]] + ) + then + ICINGA_CERT_SERVICE=true + + export ICINGA_CERT_SERVICE_BA_USER + export ICINGA_CERT_SERVICE_BA_PASSWORD + export ICINGA_CERT_SERVICE_API_USER + export ICINGA_CERT_SERVICE_API_PASSWORD + export ICINGA_CERT_SERVICE_SERVER + export ICINGA_CERT_SERVICE_PORT + export ICINGA_CERT_SERVICE_PATH + export ICINGA_CERT_SERVICE + fi +} diff --git a/rootfs/init/icinga_types/master.sh b/rootfs/init/icinga_types/master.sh index 2c4feca..a06a5eb 100644 --- a/rootfs/init/icinga_types/master.sh +++ b/rootfs/init/icinga_types/master.sh @@ -1,26 +1,20 @@ # restore a old zone file for automatic generated satellites # -restore_old_zone_config() { +restore_backup() { # backwards compatibility # in an older version, we create all zone config files in an seperate directory # [[ -d ${ICINGA_LIB_DIR}/backup/automatic-zones.d ]] && mv ${ICINGA_LIB_DIR}/backup/automatic-zones.d ${ICINGA_LIB_DIR}/backup/zones.d - if [[ -d ${ICINGA_LIB_DIR}/backup/zones.d ]] + if [[ -d ${ICINGA_LIB_DIR}/backup ]] then - log_info "restore older zone configurations" - - rsync \ - --archive \ - --recursive \ - --delete \ - --include="zones.d/***" \ - --include="zones.*" \ - # TODO api-users.conf - --exclude='*' \ - ${ICINGA_LIB_DIR}/backup/* /etc/icinga2/ + log_info "restore backup" + + [[ -f ${ICINGA_LIB_DIR}/backup/zones.conf ]] && cp -a ${ICINGA_LIB_DIR}/backup/zones.conf /etc/icinga2/zones.conf + [[ -d ${ICINGA_LIB_DIR}/backup/zones.d ]] && cp -ar ${ICINGA_LIB_DIR}/backup/zones.d/* /etc/icinga2/zones.d/ + [[ -f ${ICINGA_LIB_DIR}/backup/conf.d/api-users.conf ]] && cp -a ${ICINGA_LIB_DIR}/backup/conf.d/api-users.conf /etc/icinga2/conf.d/api-users.conf fi } @@ -33,7 +27,7 @@ configure_icinga2_master() { create_ca - restore_old_zone_config + restore_backup # copy master specific configurations # diff --git a/rootfs/init/run.sh b/rootfs/init/run.sh index 0c995a7..2f0afb4 100755 --- a/rootfs/init/run.sh +++ b/rootfs/init/run.sh @@ -76,6 +76,7 @@ run() { . /init/common.sh prepare + validate_cert-service_environment . /init/database/mysql.sh . /init/configure_icinga.sh @@ -96,11 +97,12 @@ run() { # nohup /init/runtime/inotify.sh > /dev/stdout 2>&1 & - env | grep ICINGA | sort + # env | grep ICINGA | sort nohup /usr/local/bin/rest-service.rb > /dev/stdout 2>&1 & else : nohup /init/runtime/ca_validator.sh > /dev/stdout 2>&1 & + if [[ ! -e /tmp/final ]] then nohup /init/runtime/zone_watcher.sh > /dev/stdout 2>&1 & diff --git a/rootfs/init/runtime/inotify.sh b/rootfs/init/runtime/inotify.sh index e0980a0..909c8ef 100755 --- a/rootfs/init/runtime/inotify.sh +++ b/rootfs/init/runtime/inotify.sh @@ -48,25 +48,26 @@ inotifywait \ # remove directory # rm -rf ${backup_directory}/${file} - - # monitor CLOSE_WRITE,CLOSE - # - elif [[ "${action}" = "CLOSE_WRITE,CLOSE" ]] - then - # use rsync for an backup - # we need only zones.conf and the complete zones.d directory - # all others are irrelevant - # - rsync \ - --archive \ - --recursive \ - --delete \ - --verbose \ - --include="zones.d/***" \ - --include="zones.*" \ - --include="conf.d" \ - --include="conf.d/api-users.conf" \ - --exclude='*' \ - ${monitored_directory}/* ${backup_directory}/ fi + +# # monitor CLOSE_WRITE,CLOSE +# # +# elif [[ "${action}" = "CLOSE_WRITE,CLOSE" ]] +# then +# # use rsync for an backup +# # we need only zones.conf and the complete zones.d directory +# # all others are irrelevant +# # +# rsync \ +# --archive \ +# --recursive \ +# --delete \ +# --verbose \ +# --include="zones.d/***" \ +# --include="zones.*" \ +# --include="conf.d" \ +# --include="conf.d/api-users.conf" \ +# --exclude='*' \ +# ${monitored_directory}/* ${backup_directory}/ +# fi done diff --git a/rootfs/init/wait_for/cert_service.sh b/rootfs/init/wait_for/cert_service.sh index f4550b5..18da017 100644 --- a/rootfs/init/wait_for/cert_service.sh +++ b/rootfs/init/wait_for/cert_service.sh @@ -5,7 +5,7 @@ wait_for_icinga_cert_service() { # the CERT-Service API use an Basic-Auth as first Authentication *AND* # use an own API Userr - if [[ ${ICINGA_CERT_SERVICE} ]] + if [[ "${ICINGA_CERT_SERVICE}" = "true" ]] then # use the new Cert Service to create and get a valide certificat for distributed icinga services @@ -77,10 +77,10 @@ wait_for_icinga_cert_service() { fi else log_warn "missing variables:" - log_warn" ICINGA_CERT_SERVICE_BA_USER: '${ICINGA_CERT_SERVICE_BA_USER}'" - log_warn" ICINGA_CERT_SERVICE_BA_PASSWORD: '${ICINGA_CERT_SERVICE_BA_PASSWORD}'" - log_warn" ICINGA_CERT_SERVICE_API_USER: '${ICINGA_CERT_SERVICE_API_USER}'" - log_warn" ICINGA_CERT_SERVICE_API_PASSWORD: '${ICINGA_CERT_SERVICE_API_PASSWORD}'" + log_warn " ICINGA_CERT_SERVICE_BA_USER: '${ICINGA_CERT_SERVICE_BA_USER}'" + log_warn " ICINGA_CERT_SERVICE_BA_PASSWORD: '${ICINGA_CERT_SERVICE_BA_PASSWORD}'" + log_warn " ICINGA_CERT_SERVICE_API_USER: '${ICINGA_CERT_SERVICE_API_USER}'" + log_warn " ICINGA_CERT_SERVICE_API_PASSWORD: '${ICINGA_CERT_SERVICE_API_PASSWORD}'" fi } From 8208e77be766cb969258483a4ad51236c84b01d1 Mon Sep 17 00:00:00 2001 From: Bodo Schulz Date: Wed, 17 Jan 2018 06:11:11 +0100 Subject: [PATCH 4/5] remove local build --- build/ruby-icinga-cert-service/Gemfile | 22 - build/ruby-icinga-cert-service/Gemfile.lock | 143 ----- build/ruby-icinga-cert-service/LICENSE | 504 ------------------ build/ruby-icinga-cert-service/README.md | 296 ---------- build/ruby-icinga-cert-service/Rakefile | 37 -- .../bin/rest-service.rb | 268 ---------- build/ruby-icinga-cert-service/bin/test.rb | 28 - .../icinga-cert-service.gemspec | 66 --- .../init-script/openrc/icinga-cert-service | 59 -- .../lib/cert-service.rb | 317 ----------- .../lib/cert-service/backup.rb | 37 -- .../lib/cert-service/certificate_handler.rb | 495 ----------------- .../lib/cert-service/configure_icinga.rb | 1 - .../lib/cert-service/endpoint_handler.rb | 117 ---- .../lib/cert-service/executor.rb | 32 -- .../lib/cert-service/in-memory-cache.rb | 41 -- .../lib/cert-service/monkey.rb | 39 -- .../lib/cert-service/version.rb | 6 - .../lib/cert-service/zone_handler.rb | 63 --- build/ruby-icinga-cert-service/lib/logging.rb | 37 -- .../ruby-icinga-cert-service/lib/logging.rb~ | 41 -- build/ruby-icinga-cert-service/lib/util.rb | 90 ---- 22 files changed, 2739 deletions(-) delete mode 100644 build/ruby-icinga-cert-service/Gemfile delete mode 100644 build/ruby-icinga-cert-service/Gemfile.lock delete mode 100644 build/ruby-icinga-cert-service/LICENSE delete mode 100644 build/ruby-icinga-cert-service/README.md delete mode 100644 build/ruby-icinga-cert-service/Rakefile delete mode 100755 build/ruby-icinga-cert-service/bin/rest-service.rb delete mode 100755 build/ruby-icinga-cert-service/bin/test.rb delete mode 100644 build/ruby-icinga-cert-service/icinga-cert-service.gemspec delete mode 100644 build/ruby-icinga-cert-service/init-script/openrc/icinga-cert-service delete mode 100644 build/ruby-icinga-cert-service/lib/cert-service.rb delete mode 100644 build/ruby-icinga-cert-service/lib/cert-service/backup.rb delete mode 100644 build/ruby-icinga-cert-service/lib/cert-service/certificate_handler.rb delete mode 100644 build/ruby-icinga-cert-service/lib/cert-service/configure_icinga.rb delete mode 100644 build/ruby-icinga-cert-service/lib/cert-service/endpoint_handler.rb delete mode 100644 build/ruby-icinga-cert-service/lib/cert-service/executor.rb delete mode 100644 build/ruby-icinga-cert-service/lib/cert-service/in-memory-cache.rb delete mode 100644 build/ruby-icinga-cert-service/lib/cert-service/monkey.rb delete mode 100644 build/ruby-icinga-cert-service/lib/cert-service/version.rb delete mode 100644 build/ruby-icinga-cert-service/lib/cert-service/zone_handler.rb delete mode 100644 build/ruby-icinga-cert-service/lib/logging.rb delete mode 100644 build/ruby-icinga-cert-service/lib/logging.rb~ delete mode 100644 build/ruby-icinga-cert-service/lib/util.rb diff --git a/build/ruby-icinga-cert-service/Gemfile b/build/ruby-icinga-cert-service/Gemfile deleted file mode 100644 index 749664e..0000000 --- a/build/ruby-icinga-cert-service/Gemfile +++ /dev/null @@ -1,22 +0,0 @@ -source 'https://rubygems.org' - -# Specify your gem's dependencies in icinga2.gemspec -group :tools do - gem 'rake' - gem 'rake-notes' - gem 'rubocop' - gem 'rubocop-checkstyle_formatter' - gem 'rspec' - gem 'rspec_junit_formatter' -end - -# gem 'rake' -gemspec - -gem 'puma', '~> 3.10' -gem 'sinatra', '~> 2.0' -gem 'sinatra-basic-auth' -gem 'rest-client', '~> 2.0' -# gem 'mini_cache', '~> 1.1' -# gem 'rufus-scheduler', '~> 3.4' -# gem 'tzinfo-data', '~> 1.2017' diff --git a/build/ruby-icinga-cert-service/Gemfile.lock b/build/ruby-icinga-cert-service/Gemfile.lock deleted file mode 100644 index 2412e96..0000000 --- a/build/ruby-icinga-cert-service/Gemfile.lock +++ /dev/null @@ -1,143 +0,0 @@ -PATH - remote: . - specs: - icinga-cert-service (0.15.11) - json (~> 2.1) - rest-client (~> 2.0) - ruby_dig (~> 0) - -GEM - remote: https://rubygems.org/ - specs: - ast (2.3.0) - builder (3.2.3) - coderay (1.1.1) - colored (1.2) - diff-lcs (1.3) - domain_name (0.5.20170404) - unf (>= 0.0.5, < 1.0.0) - et-orbi (1.0.8) - tzinfo - ffi (1.9.18) - guard (0.10.0) - ffi (>= 0.5.0) - thor (~> 0.14.6) - guard-rspec (0.7.3) - guard (>= 0.10.0) - http-cookie (1.0.3) - domain_name (~> 0.5) - json (2.1.0) - method_source (0.8.2) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) - mini_cache (1.1.0) - mustermann (1.0.0) - netrc (0.11.0) - parallel (1.11.2) - parser (2.4.0.0) - ast (~> 2.2) - powerpack (0.1.1) - pry (0.10.4) - coderay (~> 1.1.0) - method_source (~> 0.8.1) - slop (~> 3.4) - pry-nav (0.2.4) - pry (>= 0.9.10, < 0.11.0) - pry-remote (0.1.8) - pry (~> 0.9) - slop (~> 3.0) - puma (3.11.0) - rack (2.0.3) - rack-protection (2.0.0) - rack - rainbow (2.2.2) - rake - rake (12.0.0) - rake-notes (0.2.0) - colored - rake - rest-client (2.0.2) - http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 4.0) - netrc (~> 0.8) - rspec (3.6.0) - rspec-core (~> 3.6.0) - rspec-expectations (~> 3.6.0) - rspec-mocks (~> 3.6.0) - rspec-core (3.6.0) - rspec-support (~> 3.6.0) - rspec-expectations (3.6.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.6.0) - rspec-mocks (3.6.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.6.0) - rspec-nc (0.3.0) - rspec (>= 3) - terminal-notifier (>= 1.4) - rspec-support (3.6.0) - rspec_junit_formatter (0.2.3) - builder (< 4) - rspec-core (>= 2, < 4, != 2.12.0) - rubocop (0.49.1) - parallel (~> 1.10) - parser (>= 2.3.3.1, < 3.0) - powerpack (~> 0.1) - rainbow (>= 1.99.1, < 3.0) - ruby-progressbar (~> 1.7) - unicode-display_width (~> 1.0, >= 1.0.1) - rubocop-checkstyle_formatter (0.4.0) - rubocop (>= 0.35.1) - ruby-progressbar (1.8.1) - ruby_dig (0.0.2) - rufus-scheduler (3.4.2) - et-orbi (~> 1.0) - sinatra (2.0.0) - mustermann (~> 1.0) - rack (~> 2.0) - rack-protection (= 2.0.0) - tilt (~> 2.0) - sinatra-basic-auth (0.1.0) - sinatra - slop (3.6.0) - terminal-notifier (1.8.0) - thor (0.14.6) - thread_safe (0.3.6) - tilt (2.0.7) - tzinfo (1.2.4) - thread_safe (~> 0.1) - tzinfo-data (1.2017.3) - tzinfo (>= 1.0.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.4) - unicode-display_width (1.2.1) - -PLATFORMS - ruby - -DEPENDENCIES - guard (~> 0) - guard-rspec (~> 0) - icinga-cert-service! - mini_cache (~> 1.1) - pry (~> 0) - pry-nav (~> 0) - pry-remote (~> 0) - puma (~> 3.10) - rake - rake-notes - rest-client (~> 2.0) - rspec - rspec-nc (~> 0) - rspec_junit_formatter - rubocop - rubocop-checkstyle_formatter - rufus-scheduler (~> 3.4) - sinatra (~> 2.0) - sinatra-basic-auth - tzinfo-data (~> 1.2017) - -BUNDLED WITH - 1.15.4 diff --git a/build/ruby-icinga-cert-service/LICENSE b/build/ruby-icinga-cert-service/LICENSE deleted file mode 100644 index 8000a6f..0000000 --- a/build/ruby-icinga-cert-service/LICENSE +++ /dev/null @@ -1,504 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random - Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! diff --git a/build/ruby-icinga-cert-service/README.md b/build/ruby-icinga-cert-service/README.md deleted file mode 100644 index 8e268b1..0000000 --- a/build/ruby-icinga-cert-service/README.md +++ /dev/null @@ -1,296 +0,0 @@ -icinga-cert-service -=================== - -The Icinga-Cert-Service is a small service for creating, downloading or signing an Icinga2 certificate. -The service can be used to connect Icinga2 satellites or agents dynamically to an Icinga2 master. - -The Cert service is implemented in ruby and offers a simple REST API. - -# Status -[![Build Status](https://travis-ci.org/bodsch/ruby-icinga-cert-service.svg)][travis] -[![Dependency Status](https://gemnasium.com/badges/github.com/bodsch/ruby-icinga-cert-service.svg)][gemnasium] - -[travis]: https://travis-ci.org/bodsch/ruby-icinga-cert-service -[gemnasium]: https://gemnasium.com/github.com/bodsch/ruby-icinga-cert-service - - -# Start - -To start them run `ruby bin/rest-service.rb` - -The following environment variables can be set: - -- `ICINGA_HOST` (default: `nil`) -- `ICINGA_API_PORT` (default: `5665`) -- `ICINGA_API_USER` (default: `root`) -- `ICINGA_API_PASSWORD` (default: `icinga`) -- `REST_SERVICE_PORT` (default: `8080`) -- `REST_SERVICE_BIND` (default: `0.0.0.0`) -- `BASIC_AUTH_USER` (default: `admin`) -- `BASIC_AUTH_PASS` (default: `admin`) - -The REST-service uses an basic-authentication for the first security step. -The second Step is an configured API user into the Icinga2-Master. -The API user credentials must be set as HTTP-Header vars (see the examples below). - -To overwrite the default configuration for the REST-Service, put a `rest-service.yaml` into `/etc` : - -```yaml ---- -icinga: - server: master-server - api: - port: 5665 - user: root - password: icinga - -rest-service: - port: 8080 - bind: 192.168.10.10 - -basic-auth: - user: ba-user - password: v2rys3cr3t -``` - - -# Who to used it - -## With Icinga2 Version 2.8, we can use the new PKI-Proxy Mode - -You can use `expect` on a *satellite* or *agent* to create an certificate request with the *icinga2 node wizard*. -(A complete `expect` example can be found below) - -```bash -expect /init/node-wizard.expect -``` - -After this, you can use the *cert-service* to sign this request: - -```bash -curl \ - --user ${ICINGA_CERT_SERVICE_BA_USER}:${ICINGA_CERT_SERVICE_BA_PASSWORD} \ - --silent \ - --request GET \ - --header "X-API-USER: ${ICINGA_CERT_SERVICE_API_USER}" \ - --header "X-API-PASSWORD: ${ICINGA_CERT_SERVICE_API_PASSWORD}" \ - --write-out "%{http_code}\n" \ - --output /tmp/sign_${HOSTNAME}.json \ - http://${ICINGA_CERT_SERVICE_SERVER}:${ICINGA_CERT_SERVICE_PORT}/v2/sign/${HOSTNAME} -``` - -## Otherwise, the pre 2.8 Mode works well - -To create a certificate: - -```bash -curl \ - --request GET \ - --user ${ICINGA_CERT_SERVICE_BA_USER}:${ICINGA_CERT_SERVICE_BA_PASSWORD} \ - --silent \ - --header "X-API-USER: ${ICINGA_CERT_SERVICE_API_USER}" \ - --header "X-API-KEY: ${ICINGA_CERT_SERVICE_API_PASSWORD}" \ - --output /tmp/request_${HOSTNAME}.json \ - http://${ICINGA_CERT_SERVICE_SERVER}:${ICINGA_CERT_SERVICE_PORT}/v2/request/${HOSTNAME} -``` - -this creates an output file, that we use to download the certificate. - -## Download the created certificate: - -```bash -checksum=$(jq --raw-output .checksum /tmp/request_${HOSTNAME}.json) - -curl \ - --request GET \ - --user ${ICINGA_CERT_SERVICE_BA_USER}:${ICINGA_CERT_SERVICE_BA_PASSWORD} \ - --silent \ - --header "X-API-USER: ${ICINGA_CERT_SERVICE_API_USER}" \ - --header "X-API-KEY: ${ICINGA_CERT_SERVICE_API_PASSWORD}" \ - --header "X-CHECKSUM: ${checksum}" \ - --output ${WORK_DIR}/pki/${HOSTNAME}/${HOSTNAME}.tgz \ - http://${ICINGA_CERT_SERVICE_SERVER}:${ICINGA_CERT_SERVICE_PORT}/v2/cert/${HOSTNAME} -``` - -## Create the Satellite `Endpoint` - -```bash -cat << EOF > /etc/icinga2/zones.conf - -/* the following line specifies that the client connects to the master and not vice versa */ -object Endpoint "${master_name}" { host = "${ICINGA_MASTER}"; port = "5665" } -object Zone "master" { endpoints = [ "${master_name}" ] } - -object Endpoint NodeName {} -object Zone ZoneName { endpoints = [ NodeName ] ; parent = "master" } - -object Zone "global-templates" { global = true } -object Zone "director-global" { global = true } - -EOF -``` - -## NOTE -The generated certificate has an timeout from 10 minutes between beginning of creation and download. - - -# API - -following API Calls are implemented: - -## Health Check - -The Health Check is important to determine whether the certificate service has started. - -```bash -curl \ - --request GET \ - --silent \ - http://${REST-SERVICE}:8080/v2/health-check -``` - -The health check returns only a string with `healthy` as content. - -## Icinga Version - -Returns the Icinga Version - -```bash -curl \ - --request GET \ - --silent \ - http://${REST-SERVICE}:8080/v2/icinga-version -``` - -The icinga version call returns only a string with the shortend version as content: `2.8` - -## create a certificate request - -Create an Certificate request - -```bash -curl \ - --user ${ICINGA_CERT_SERVICE_BA_USER}:${ICINGA_CERT_SERVICE_BA_PASSWORD} \ - --request GET \ - --header "X-API-USER: cert-service" \ - --header "X-API-KEY: knockknock" \ - http://${REST-SERVICE}:8080/v2/request/${HOST-NAME} -``` - -## download an certificate - -After an certificate request, you can download the created certificate: - -```bash -curl \ - --user ${ICINGA_CERT_SERVICE_BA_USER}:${ICINGA_CERT_SERVICE_BA_PASSWORD} \ - --request GET \ - --header "X-API-USER: cert-service" \ - --header "X-API-KEY: knockknock" \ - --output /tmp/${HOST-NAME}.tgz \ - http://${REST-SERVICE}:8080/v2/cert/${HOST-NAME} -``` - -## validate the satellite CA - -If the CA has been renewed on the master, all satellites or agents will no longer be able to connect to the master. -To be able to detect this possibility, you can create a checksum of the `ca.crt` file and have it checked by the certificats service. - -The following algorithms are supported to create a checksum: -- ´md5` -- ´sha256` -- ´sha384` -- ´sha512` - -```bash -checksum=$(sha256sum ${ICINGA_CERT_DIR}/ca.crt | cut -f 1 -d ' ') - -curl \ - --request GET \ - http://${REST-SERVICE}:8080/v2/validate/${checksum} -``` - -## sign a certificate request - -Version 2.8 of Icinga2 came with a CA proxy. -Here you can use the well-known `node wizard` to create a certificate request on a satellite or agent. -This certificate only has to be confirmed at the Icinga2 Master. - -The certificate files are then replicated to the respective applicant. - -With the following API call you can confirm the certificate without being logged on to the master. - -```bash -curl \ - --user ${ICINGA_CERT_SERVICE_BA_USER}:${ICINGA_CERT_SERVICE_BA_PASSWORD} \ - --request POST \ - --header "X-API-USER: cert-service" \ - --header "X-API-KEY: knockknock" \ - http://${REST-SERVICE}:8080/v2/sign/${HOST-NAME} -``` - -The `node wizard` can also be automated (via `expect`): - -``` - -cat << EOF >> ~/node-wizard.expect - -#!/usr/bin/expect - -# exp_internal 1 - -log_user 1 -set timeout 3 - -spawn icinga2 node wizard - -expect -re "Please specify if this is a satellite/client setup" { - send -- "y\r" -} -expect -re "Please specify the common name " { - send -- "[exec hostname -f]\r" -} -expect -re "Master/Satellite Common Name" { - send -- "$env(ICINGA_MASTER)\r" -} -expect -re "Do you want to establish a connection to the parent node" { - send -- "y\r" -} -expect -re "endpoint host" { - send -- "$env(ICINGA_MASTER)\r" -} -expect -re "endpoint port" { - send -- "5665\r" -} -expect -re "Add more master/satellite endpoints" { - send -- "n\r" -} -expect -re "Is this information correct" { - send -- "y\r" -} -expect -re "Please specify the request ticket generated on your Icinga 2 master" { - send -- "\r" -} -expect -re "Bind Host" { - send -- "\r" -} -expect -re "Bind Port" { - send -- "\r" -} -expect -re "config from parent node" { - send -- "y\r" -} -expect -re "commands from parent node" { - send -- "y\r" -} - -interact - -EOF - - -expect ~/node-wizard.expect 1> /dev/null - -``` - - diff --git a/build/ruby-icinga-cert-service/Rakefile b/build/ruby-icinga-cert-service/Rakefile deleted file mode 100644 index ad2e817..0000000 --- a/build/ruby-icinga-cert-service/Rakefile +++ /dev/null @@ -1,37 +0,0 @@ - -require 'bundler/gem_tasks' -require 'rspec/core/rake_task' -require 'bundler/gem_tasks' -require 'rubocop/rake_task' -require 'rake/testtask' - -# Default directory to look in is `/specs` -# Run with `rake spec` -RSpec::Core::RakeTask.new(:spec) do |task| - task.rspec_opts = ['--color'] -end - -desc 'Run all style checks' -task :style => ['style:ruby'] - -desc 'Run all regular tasks' -task :default => :spec - -desc 'Run all tests' -task :test => ['test'] - -namespace :style do - desc 'Run Ruby style checks' - RuboCop::RakeTask.new(:ruby) do |task| - task.patterns = ['**/*.rb'] - # don't abort rake on failure - task.fail_on_error = false - end -end - - -Rake::TestTask.new("test:all") do |t| - t.libs = ["lib", "spec"] - t.warning = true - t.test_files = FileList['spec/**/*_spec.rb'] -end diff --git a/build/ruby-icinga-cert-service/bin/rest-service.rb b/build/ruby-icinga-cert-service/bin/rest-service.rb deleted file mode 100755 index 20a83c9..0000000 --- a/build/ruby-icinga-cert-service/bin/rest-service.rb +++ /dev/null @@ -1,268 +0,0 @@ -#!/usr/bin/env ruby -# -# 05.10.2016 - Bodo Schulz -# -# -# v2.1.0 - -# ----------------------------------------------------------------------------- - -require 'ruby_dig' if RUBY_VERSION < '2.3' - -require 'sinatra/base' -require 'sinatra/basic_auth' -require 'json' -require 'yaml' - -require_relative '../lib/cert-service' -require_relative '../lib/logging' - -# ----------------------------------------------------------------------------- - -module Sinatra - class CertServiceRest < Base - register Sinatra::BasicAuth - - include Logging - - @icinga_master = ENV.fetch('ICINGA_HOST' , nil) - @icinga_api_port = ENV.fetch('ICINGA_API_PORT' , 5665 ) - @icinga_api_user = ENV.fetch('ICINGA_API_USER' , 'root' ) - @icinga_api_password = ENV.fetch('ICINGA_API_PASSWORD', 'icinga' ) - @rest_service_port = ENV.fetch('REST_SERVICE_PORT' , 8080 ) - @rest_service_bind = ENV.fetch('REST_SERVICE_BIND' , '0.0.0.0' ) - @basic_auth_user = ENV.fetch('BASIC_AUTH_USER' , 'admin') - @basic_auth_pass = ENV.fetch('BASIC_AUTH_PASS' , 'admin') - - configure do - set :environment, :production - - # default configuration - @rest_service_port = 8080 - @rest_service_bind = '0.0.0.0' - - if( File.exist?('/etc/rest-service.yaml') ) - - config = YAML.load_file('/etc/rest-service.yaml') - - @icinga_master = config.dig('icinga', 'server') - @icinga_api_port = config.dig('icinga', 'api', 'port') || 5665 - @icinga_api_user = config.dig('icinga', 'api', 'user') || 5665 - @icinga_api_password = config.dig('icinga', 'api', 'password') || 5665 - @rest_service_port = config.dig('rest-service', 'port') || 8080 - @rest_service_bind = config.dig('rest-service', 'bind') || '0.0.0.0' - @basic_auth_user = config.dig('basic-auth', 'user') || 'admin' - @basic_auth_pass = config.dig('basic-auth', 'password') || 'admin' - else - puts 'no configuration exists, use default settings' - end - end - - set :logging, true - set :app_file, caller_files.first || $PROGRAM_NAME - set :run, proc { $PROGRAM_NAME == app_file } - set :dump_errors, true - set :show_exceptions, true - set :public_folder, '/var/www/' - - set :bind, @rest_service_bind - set :port, @rest_service_port.to_i - - # ----------------------------------------------------------------------------- - - error do - msg = "ERROR\n\nThe cert-rest-service has nasty error - " + env['sinatra.error'] - - msg.message - end - - # ----------------------------------------------------------------------------- - - before do - content_type :json - end - - before '/v2/*/:host' do - request.body.rewind - @request_paylod = request.body.read - end - - # ----------------------------------------------------------------------------- - - # configure Basic Auth - authorize 'API' do |username, password| - username == @basic_auth_user && password == @basic_auth_pass - end - - # ----------------------------------------------------------------------------- - - config = { - icinga: { - server: @icinga_master, - api: { - port: @icinga_api_port, - user: @icinga_api_user, - password: @icinga_api_password, - pki_path: @icinga_api_pki_path, - node_name: @icinga_api_node_name - } - } - } - - ics = IcingaCertService::Client.new(config) - - get '/v2/health-check' do - status 200 - 'healthy' - end - - get '/v2/icinga-version' do - status 200 - result = ics.icinga_version - result + "\n" - end - - # curl \ - # -u "foo:bar" \ - # --request GET \ - # --header "X-API-USER: cert-service" \ - # --header "X-API-KEY: knockknock" \ - # http://$REST-SERVICE:8080/v2/request/$HOST-NAME - # - protect 'API' do - get '/v2/request/:host' do - result = ics.create_certificate(host: params[:host], request: request.env) - result_status = result.dig(:status).to_i - - status result_status - - JSON.pretty_generate(result) + "\n" - end - end - - # curl \ - # -u "foo:bar" \ - # --request POST \ - # http://$REST-SERVICE:8080/v2/ticket/$HOST-NAME - # - protect 'API' do - post '/v2/ticket/:host' do - status 200 - - result = ics.create_ticket(host: params[:host]) - - JSON.pretty_generate(result) + "\n" - end - end - - # curl \ - # -u "foo:bar" \ - # --request GET \ - # http://$REST-SERVICE:8080/v2/validate/$CHECKSUM - # - protect 'API' do - get '/v2/validate/:checksum' do - result = ics.validate_certificate(checksum: params[:checksum]) - result_status = result.dig(:status).to_i - - status result_status - content_type :json - JSON.pretty_generate(result) + "\n" - end - end - - - # curl \ - # -u "foo:bar" \ - # --request GET \ - # --header "X-API-USER: cert-service" \ - # --header "X-API-KEY: knockknock" \ - # --header "X-CHECKSUM: ${checksum}" \ - # --output /tmp/$HOST-NAME.tgz \ - # http://$REST-SERVICE:8080/v2/cert/$HOST-NAME - # - protect 'API' do - get '/v2/cert/:host' do - result = ics.check_certificate( host: params[:host], request: request.env ) - - logger.debug(result) - - result_status = result.dig(:status).to_i - - if result_status == 200 - - path = result.dig(:path) - file_name = result.dig(:file_name) - - status result_status - - send_file(format('%s/%s', path, file_name), filename: file_name, type: 'Application/octet-stream') - else - - status result_status - - JSON.pretty_generate(result) + "\n" - end - end - end - - # curl \ - # -u "foo:bar" \ - # --request GET \ - # --header "X-API-USER: cert-service" \ - # --header "X-API-KEY: knockknock" \ - # --header "X-CHECKSUM: ${checksum}" \ - # --output /tmp/ca.crt \ - # http://$REST-SERVICE:8080/v2/master-ca - # - protect 'API' do - get '/v2/master-ca' do - - path= '/var/lib/icinga2' - file_name = 'ca.crt' - if( File.exist?(format('%s/%s', path, file_name) ) ) - status 200 - send_file(format('%s/%s', path, file_name), filename: file_name, type: 'Application/octet-stream') - else - - status 404 - - JSON.pretty_generate('no ca file found') + "\n" - end - end - end - - # curl \ - # -u "foo:bar" \ - # --request POST \ - # --header "X-API-USER: cert-service" \ - # --header "X-API-KEY: knockknock" \ - # http://$REST-SERVICE:8080/v2/sign/$HOST-NAME - # - protect 'API' do - get '/v2/sign/:host' do - status 200 - - result = ics.sign_certificate(host: params[:host], request: request.env) - - JSON.pretty_generate(result) + "\n" - end - end - - - not_found do - jj = { - 'meta' => { - 'code' => 404, - 'message' => 'Request not found.' - } - } - content_type :json - JSON.pretty_generate(jj) - end - - # ----------------------------------------------------------------------------- - run! if app_file == $PROGRAM_NAME - # ----------------------------------------------------------------------------- - end -end diff --git a/build/ruby-icinga-cert-service/bin/test.rb b/build/ruby-icinga-cert-service/bin/test.rb deleted file mode 100755 index d993308..0000000 --- a/build/ruby-icinga-cert-service/bin/test.rb +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env ruby -# -# 05.10.2016 - Bodo Schulz -# -# -# v2.1.0 - -# ----------------------------------------------------------------------------- - -require 'ruby_dig' if RUBY_VERSION < '2.3' - -require 'sinatra/base' -require 'sinatra/basic_auth' -require 'json' -require 'yaml' - -require_relative '../lib/cert-service' -require_relative '../lib/logging' - -# ----------------------------------------------------------------------------- - -config = { - icinga_master: 'localhost' -} - -ics = IcingaCertService::Client.new(config) - -# ----------------------------------------------------------------------------- diff --git a/build/ruby-icinga-cert-service/icinga-cert-service.gemspec b/build/ruby-icinga-cert-service/icinga-cert-service.gemspec deleted file mode 100644 index 1ebbc6c..0000000 --- a/build/ruby-icinga-cert-service/icinga-cert-service.gemspec +++ /dev/null @@ -1,66 +0,0 @@ - -lib = File.expand_path('../lib', __FILE__) -$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'cert-service/version' - -Gem::Specification.new do |s| - - s.name = 'icinga-cert-service' - s.version = IcingaCertService::VERSION - s.date = '2017-10-19' - s.summary = 'Icinga Certificate Service' - s.description = 'Ruby Class to create an provide a Icinga2 Certificate for Satellites or Agents ' - s.authors = ['Bodo Schulz'] - s.email = 'bodo@boone-schulz.de' - - s.files = Dir[ - 'README.md', - 'LICENSE', - 'lib/**/*', - 'doc/*', - 'examples/*.rb' - ] - - s.homepage = 'https://github.com/bodsch/ruby-icinga-cert-service' - s.license = 'LGPL-2.1+' - - begin - - if( RUBY_VERSION >= '2.0' ) - s.required_ruby_version = '~> 2.0' - elsif( RUBY_VERSION <= '2.1' ) - s.required_ruby_version = '~> 2.1' - elsif( RUBY_VERSION <= '2.2' ) - s.required_ruby_version = '~> 2.2' - elsif( RUBY_VERSION <= '2.3' ) - s.required_ruby_version = '~> 2.3' - end - - if( RUBY_VERSION < '2.3' ) - s.add_dependency('ruby_dig', '~> 0') - end - - if( RUBY_VERSION >= '2.3' ) - s.add_dependency('openssl', '~> 2.0') - end - rescue => e - warn "#{$0}: #{e}" - exit! - end - -# s.required_ruby_version = '>= 2.3' -# s.add_dependency('openssl', '~> 2.0') - - s.add_dependency('rest-client', '~> 2.0') - s.add_dependency('json', '~> 2.1') - - s.add_development_dependency('rspec', '~> 0') - s.add_development_dependency('rspec-nc', '~> 0') - s.add_development_dependency('guard', '~> 0') - s.add_development_dependency('guard-rspec', '~> 0') - s.add_development_dependency('pry', '~> 0') - s.add_development_dependency('pry-remote', '~> 0') - s.add_development_dependency('pry-nav', '~> 0') - -end - diff --git a/build/ruby-icinga-cert-service/init-script/openrc/icinga-cert-service b/build/ruby-icinga-cert-service/init-script/openrc/icinga-cert-service deleted file mode 100644 index f90bf68..0000000 --- a/build/ruby-icinga-cert-service/init-script/openrc/icinga-cert-service +++ /dev/null @@ -1,59 +0,0 @@ -#!/sbin/openrc-run -# Copyright 1999-2016 Gentoo Foundation -# Distributed under the terms of the GNU General Public License v2 - -: CFGFILE=${CFGFILE:=/etc/conf.d/icinga2-cert-service} - -depend() { - use net - config ${CFGFILE} -} - -get_config() { - x=$1 - test -e ${CFGFILE} || return 1 - . ${CFGFILE} -} - -extra_started_commands='reload' -command=${CERT_SERVICE_BIN:-/usr/local/bin/icinga2-cert-service.rb} -command_args= -pidfile=/var/run/icinga2-cert-service.pid -description="Icinga2 certificate service" - -checkconfig() { - get_config - DAEMON=${CERT_SERVICE_BIN:-/usr/local/bin/icinga2-cert-service.rb} - pidfile="/var/run/icinga2-cert-service.pid" - LOGFILE="/var/log/icinga2-cert-service.log" -} - -start() { - checkconfig || return 1 - - ebegin "Starting icinga2 certificate service" - start-stop-daemon \ - ${DEBUG:+"--verbose"} \ - --start \ - --exec "${DAEMON}" \ - --make-pidfile \ - --pidfile "${pidfile}" \ - --background \ - -- > $LOGFILE 2>&1 - - local retval=$? - if [ $retval -ne 0 ]; then - ewarn "Error starting icinga2 certificate service. '$LOGFILE' for details." - fi - eend $retval -} - -stop() { - ebegin "Stopping icinga2 certificate service" - start-stop-daemon \ - --stop \ - --pidfile "${pidfile}" \ - --retry "SIGTERM/15 SIGKILL/30" \ - --progress - eend $? -} diff --git a/build/ruby-icinga-cert-service/lib/cert-service.rb b/build/ruby-icinga-cert-service/lib/cert-service.rb deleted file mode 100644 index 2ec2b55..0000000 --- a/build/ruby-icinga-cert-service/lib/cert-service.rb +++ /dev/null @@ -1,317 +0,0 @@ -# -# -# - -require 'socket' -require 'open3' -require 'fileutils' -require 'rest-client' -# require 'mini_cache' -# require 'rufus-scheduler' - -require_relative 'logging' -require_relative 'util' -require_relative 'cert-service/version' -require_relative 'cert-service/monkey' -require_relative 'cert-service/executor' -require_relative 'cert-service/certificate_handler' -require_relative 'cert-service/endpoint_handler' -require_relative 'cert-service/zone_handler' -require_relative 'cert-service/in-memory-cache' -require_relative 'cert-service/backup' - -# ----------------------------------------------------------------------------- - -# -# -# -module IcingaCertService - # Client Class to create on-the-fly a certificate to connect automaticly as satellite to an icinga2-master - # - # - class Client - - include Logging - include Util::Tar - include IcingaCertService::Executor - include IcingaCertService::CertificateHandler - include IcingaCertService::EndpointHandler - include IcingaCertService::ZoneHandler - include IcingaCertService::InMemoryDataCache - include IcingaCertService::Backup - - attr_accessor :icinga_version - - # create a new instance - # - # @param [Hash, #read] params to configure the Client - # @option params [String] :icinga_master The name (FQDN or IP) of the icinga2 master - # - # @example - # IcingaCertService::Client.new( icinga_master: 'icinga2-master.example.com' ) - # - def initialize( settings ) - - raise ArgumentError.new('only Hash are allowed') unless( settings.is_a?(Hash) ) - raise ArgumentError.new('missing settings') if( settings.size.zero? ) - - @icinga_master = settings.dig(:icinga, :server) - @icinga_port = settings.dig(:icinga, :api, :port) || 5665 - @icinga_api_user = settings.dig(:icinga, :api, :user) || 'root' - @icinga_api_password = settings.dig(:icinga, :api, :password) || 'icinga' - - raise ArgumentError.new('missing \'icinga server\'') if( @icinga_master.nil? ) - - raise ArgumentError.new(format('wrong type. \'icinga api port\' must be an Integer, given \'%s\'', @icinga_port.class.to_s)) unless( @icinga_port.is_a?(Integer) ) - raise ArgumentError.new(format('wrong type. \'icinga api user\' must be an String, given \'%s\'' , @icinga_api_user.class.to_s)) unless( @icinga_api_user.is_a?(String) ) - raise ArgumentError.new(format('wrong type. \'icinga api password\' must be an String, given \'%s\'', @icinga_api_password.class.to_s)) unless( @icinga_api_password.is_a?(String) ) - - @tmp_directory = '/tmp/icinga-pki' - - version = IcingaCertService::VERSION - date = '2018-01-18' - detect_version - - logger.info('-----------------------------------------------------------------') - logger.info(format(' certificate service for Icinga2 (%s)', @icinga_version)) - logger.info(format(' Version %s (%s)', version, date)) - logger.info(' Copyright 2017-2018 Bodo Schulz') - logger.info('-----------------------------------------------------------------') - logger.info('') - -# @cache = MiniCache::Store.new - # run internal scheduler to remove old data -# scheduler = Rufus::Scheduler.new -# -# scheduler.every( '30s', :first_in => '30s' ) do -# restarter() -# end - - end - - # - # - # - # - def detect_version - - max_retries = 20 - sleep_between_retries = 8 - retried = 0 - - @icinga_version = 'unknown' - - begin - #response = rest_client.get( headers ) - response = RestClient::Request.execute( - method: :get, - url: format('https://%s:%d/v1/status/IcingaApplication', @icinga_master, @icinga_port ), - timeout: 5, - headers: { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }, - user: @icinga_api_user, - password: @icinga_api_password, - verify_ssl: OpenSSL::SSL::VERIFY_NONE - ) - - response = response.body if(response.is_a?(RestClient::Response)) - response = JSON.parse(response) if(response.is_a?(String)) - results = response.dig('results') if(response.is_a?(Hash)) - results = results.first if(results.is_a?(Array)) - app_data = results.dig('status','icingaapplication','app') - version = app_data.dig('version') if(app_data.is_a?(Hash)) - - if(version.is_a?(String)) - parts = version.match(/^r(?[0-9]+\.{0}\.[0-9]+)(.*)/i) - @icinga_version = parts['v'].to_s.strip if(parts) - end - - rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH => e - sleep( sleep_between_retries ) - retry - rescue RestClient::ExceptionWithResponse => e - - if( retried < max_retries ) - retried += 1 - logger.debug( format( 'connection refused (retry %d / %d)', retried, max_retries ) ) - sleep( sleep_between_retries ) - retry - else - raise format( 'Maximum retries (%d) reached. Giving up ...', max_retries ) - end - end - end - - # function to read API Credentials from icinga2 Configuration - # - # @param [Hash, #read] params - # @option params [String] :api_user the API User, default is 'cert-service' - # - # @example - # read_api_credentials( api_user: 'admin' ) - # - # @return [String, #read] the configured Password or nil - # - def read_api_credentials(params = {}) - - api_user = params.dig(:api_user) || 'cert-service' - - file_name = '/etc/icinga2/conf.d/api-users.conf' - - file = File.open(file_name, 'r') - contents = file.read - password = nil - - regexp_long = / # Match she-bang style C-comment - \/\* # Opening delimiter. - [^*]*\*+ # {normal*} Zero or more non-*, one or more * - (?: # Begin {(special normal*)*} construct. - [^*\/] # {special} a non-*, non-\/ following star. - [^*]*\*+ # More {normal*} - )* # Finish "Unrolling-the-Loop" - \/ # Closing delimiter. - /x - - regex = /\"#{api_user}\"(.*){(.*)password(.*)=(.*)\"(?.+[a-zA-Z0-9])\"(.*)}\n/m - - # remove comments - result = contents.gsub(regexp_long, '') - - # split our string into more parts - result = result.split('object ApiUser') - - # now, iterate over all blocks and get the password - # - result.each do |block| - password = block.scan(regex) - - next unless password.is_a?(Array) && password.count == 1 - - password = password.flatten.first - break - end - - password - end - - # add a host to 'api-users.conf' - # - # https://monitoring-portal.org/index.php?thread/41172-icinga2-api-mit-zertifikaten/&postID=251902#post251902 - # - # @param [Hash, #read] params - # @option params [String] :host - # - # @example - # add_api_user( host: 'icinga2-satellite' ) - # - # @return [Hash, #read] if config already created: - # * :status [Integer] 204 - # * :message [String] Message - # @return nil if successful - # - def add_api_user(params) - - host = params.dig(:host) - - return { status: 500, message: 'no hostname to create an api user' } if( host.nil? ) - - file_name = '/etc/icinga2/conf.d/api-users.conf' - - return { status: 500, message: format( 'api user not successful configured! file %s missing', file_name ) } unless( File.exist?(file_name) ) - - file = File.open(file_name, 'r') - contents = file.read - - regexp_long = / # Match she-bang style C-comment - \/\* # Opening delimiter. - [^*]*\*+ # {normal*} Zero or more non-*, one or more * - (?: # Begin {(special normal*)*} construct. - [^*\/] # {special} a non-*, non-\/ following star. - [^*]*\*+ # More {normal*} - )* # Finish "Unrolling-the-Loop" - \/ # Closing delimiter. - /x - result = contents.gsub(regexp_long, '') - - scan_api_user = result.scan(/object ApiUser(.*)"(?.+\S)"/).flatten - - return { status: 200, message: format('the configuration for the api user %s already exists', host) } if( scan_api_user.include?(host) == true ) - - logger.debug(format('i miss an configuration for api user %s', host)) - - File.open(file_name, 'a') do |f| - f << "/*\n" - f << " * generated at #{Time.now} with certificate service for Icinga2 #{IcingaCertService::VERSION}\n" - f << " */\n" - f << "object ApiUser \"#{host}\" {\n" - f << " client_cn = \"#{host}\"\n" - f << " permissions = [ \"*\" ]\n" - f << "}\n\n" - end - - return { status: 200, message: format('configuration for api user %s has been created', host) } - end - - # reload the icinga2-master using the api - # - # @param [Hash, #read] params - # - # @option params [String] :request - # * HTTP_X_API_USER - # * HTTP_X_API_PASSWORD - # - def reload_icinga_config(params) - - logger.info( 'restart icinga2 process') - - api_user = params.dig(:request, 'HTTP_X_API_USER') - api_password = params.dig(:request, 'HTTP_X_API_PASSWORD') - - return { status: 500, message: 'missing API Credentials - API_USER' } if( api_user.nil?) - return { status: 500, message: 'missing API Credentials - API_PASSWORD' } if( api_password.nil? ) - - password = read_api_credentials( api_user: api_user ) - - return { status: 500, message: 'wrong API Credentials' } if( password.nil? || api_password != password ) - - options = { user: api_user, password: api_password, verify_ssl: OpenSSL::SSL::VERIFY_NONE } - headers = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' } - url = format('https://%s:5665/v1/actions/restart-process', @icinga_master ) - - rest_client = RestClient::Resource.new( URI.encode( url ), options ) - - begin - - response = rest_client.post( {}.to_json, headers ) - - response = response.body if(response.is_a?(RestClient::Response)) - response = JSON.parse(response) if(response.is_a?(String)) - - logger.debug(JSON.pretty_generate(response)) - - rescue RestClient::ExceptionWithResponse => e - - logger.error("Error: restart-process has failed: '#{e}'") - logger.error(JSON.pretty_generate(params)) - - return { status: 500, message: e } - end - - { status: 200, message: 'service restarted' } - end - - -# def restarter() -# logger.debug( " => restarter" ) -# restart = @cache.get( 'reload' ) -# # logger.debug( "cache: #{restart}" ) -# unless( restart.nil? ) -# host = restart.dig(:host) -# logger.debug( "restart icinga service (#{host})") -# reload_icinga_config(restart) -# -# @cache.unset( 'reload' ) -# end -# end - - end -end diff --git a/build/ruby-icinga-cert-service/lib/cert-service/backup.rb b/build/ruby-icinga-cert-service/lib/cert-service/backup.rb deleted file mode 100644 index fa6123b..0000000 --- a/build/ruby-icinga-cert-service/lib/cert-service/backup.rb +++ /dev/null @@ -1,37 +0,0 @@ - -module IcingaCertService - # - # - # - module Backup - - # create Backup of generated files - # - def create_backup - - source_directory = '/etc/icinga2' - backup_directory = '/var/lib/icinga2/backup' - - FileUtils.mkpath(backup_directory) unless File.exist?(backup_directory) - - white_list = %w(zones.d zones.conf conf.d/api-users.conf) - - white_list.each do |p| - - source_file = format( '%s/%s', source_directory, p ) - - destination_directory = File.dirname( source_file ) - destination_directory.gsub!( source_directory, backup_directory ) - - FileUtils.mkpath(destination_directory) unless File.exist?(destination_directory) - - if( File.directory?(source_file) ) - FileUtils.cp_r(source_file, "#{backup_directory}/", :noop => false, :verbose => false ) - else - FileUtils.cp_r(source_file, "#{backup_directory}/#{p}", :noop => false, :verbose => false ) - end - end - - end - end -end diff --git a/build/ruby-icinga-cert-service/lib/cert-service/certificate_handler.rb b/build/ruby-icinga-cert-service/lib/cert-service/certificate_handler.rb deleted file mode 100644 index 745c56c..0000000 --- a/build/ruby-icinga-cert-service/lib/cert-service/certificate_handler.rb +++ /dev/null @@ -1,495 +0,0 @@ - -require 'open3' - -require_relative 'monkey' - -module IcingaCertService - - module CertificateHandler - - # create a certificate - # - # @param [Hash, #read] params - # @option params [String] :host - # @option params [Hash] :request - # - # @example - # create_certificate( host: 'icinga2-satellite', request: { 'HTTP_X_API_USER => 'admin', HTTP_X_API_PASSWORD' => 'admin' } } ) - # - # @return [Hash, #read] - # * :status [Integer] 200 for successful, or 500 for an error - # * :master_name [String] the Name of the Icinga2-master (need to configure the satellite correctly) - # * :master_ip [String] the IP of the Icinga2-master (need to configure the satellite correctly) - # * :checksum [String] a created Checksum to retrive the certificat archive - # * :timestamp [Integer] a timestamp for the created archive - # * :timeout [Integer] the timeout for the created archive - # * :file_name [String] the archive Name - # * :path [String] the Path who stored the certificate archive - # - def create_certificate( params ) - - host = params.dig(:host) - api_user = params.dig(:request, 'HTTP_X_API_USER') - api_password = params.dig(:request, 'HTTP_X_API_PASSWORD') - - return { status: 500, message: 'no hostname' } if( host.nil? ) - return { status: 500, message: 'missing API Credentials - API_USER' } if( api_user.nil?) - return { status: 500, message: 'missing API Credentials - API_PASSWORD' } if( api_password.nil? ) - - password = read_api_credentials( api_user: api_user ) - - return { status: 500, message: 'wrong API Credentials' } if( password.nil? || api_password != password ) - - if( @icinga_master.nil? ) - begin - server_name = Socket.gethostbyname(Socket.gethostname).first - rescue => e - logger.error(e) - - server_name = @icinga_master - else - server_ip = IPSocket.getaddress(Socket.gethostname) - end - else - server_name = @icinga_master - - begin - server_ip = IPSocket.getaddress(server_name) - rescue => e - logger.error(server_name) - logger.error(e) - - server_ip = '127.0.0.1' - end - end - - pki_base_directory = '/etc/icinga2/pki' - pki_base_directory = '/var/lib/icinga2/certs' if( @icinga_version == '2.8' ) - - return { status: 500, message: 'no PKI directory found. Please configure first the Icinga2 Master!' } if( pki_base_directory.nil? ) - - pki_master_key = format('%s/%s.key', pki_base_directory, server_name) - pki_master_csr = format('%s/%s.csr', pki_base_directory, server_name) - pki_master_crt = format('%s/%s.crt', pki_base_directory, server_name) - pki_master_ca = format('%s/ca.crt', pki_base_directory) - - return { status: 500, message: 'no PKI directory found. Please configure first the Icinga2 Master!' } unless( File.exist?(pki_base_directory) ) - - zone_base_directory = '/etc/icinga2/zone.d' - - FileUtils.mkpath( format('%s/global-templates', zone_base_directory) ) - FileUtils.mkpath( format('%s/%s', zone_base_directory, host) ) - - # - unless File.exist?(format('%s/global-templates/services.conf', zone_base_directory) ) - - if( File.exist?('/etc/icinga2/conf.d/services.conf') ) - FileUtils.mv('/etc/icinga2/conf.d/services.conf', format('%s/global-templates/services.conf', zone_base_directory)) - else - logger.error('missing services.conf under /etc/icinga2/conf.d') - end - end - - logger.debug(format('search PKI files for the Master \'%s\'', server_name)) - - if( !File.exist?(pki_master_key) || !File.exist?(pki_master_csr) || !File.exist?(pki_master_crt) ) - logger.error('missing file') - logger.debug(pki_master_key) - logger.debug(pki_master_csr) - logger.debug(pki_master_crt) - - return { status: 500, message: format('missing PKI for Icinga2 Master \'%s\'', server_name) } - end - - tmp_host_directory = format('%s/%s', @tmp_directory, host) - # uid = File.stat('/etc/icinga2/conf.d').uid - # gid = File.stat('/etc/icinga2/conf.d').gid - - FileUtils.rmdir(tmp_host_directory) if(File.exist?(tmp_host_directory)) - FileUtils.mkpath(tmp_host_directory) unless File.exist?(tmp_host_directory) - FileUtils.chmod_R(0o777, @tmp_directory) if File.exist?(tmp_host_directory) - - return { status: 500, message: 'can\'t create temporary directory' } unless File.exist?(tmp_host_directory) - - salt = Digest::SHA256.hexdigest(host) - - pki_satellite_key = format('%s/%s.key', tmp_host_directory, host) - pki_satellite_csr = format('%s/%s.csr', tmp_host_directory, host) - pki_satellite_crt = format('%s/%s.crt', tmp_host_directory, host) - pki_ticket = '%PKI_TICKET%' - - commands = [] - - # icinga2 pki new-cert --cn $node --csr $node.csr --key $node.key - # icinga2 pki sign-csr --csr $node.csr --cert $node.crt - commands << format('icinga2 pki new-cert --cn %s --key %s --csr %s', host, pki_satellite_key, pki_satellite_csr) - commands << format('icinga2 pki sign-csr --csr %s --cert %s', pki_satellite_csr, pki_satellite_crt) - - if( @icinga_version == '2.7' ) - commands << format('icinga2 pki save-cert --key %s --cert %s --trustedcert %s/trusted-master.crt --host %s', pki_satellite_key, pki_satellite_crt, tmp_host_directory, server_name) - commands << format('icinga2 pki ticket --cn %s --salt %s', server_name, salt) - commands << format('icinga2 pki request --host %s --port 5665 --ticket %s --key %s --cert %s --trustedcert %s/trusted-master.crt --ca %s', server_name, pki_ticket, pki_satellite_key, pki_satellite_crt, tmp_host_directory, pki_master_ca) - end - - pki_ticket = nil - next_command = nil - - commands.each_with_index do |c, index| - - next_command = commands[index + 1] - result = exec_command(cmd: c) - exit_code = result.dig(:code) - exit_message = result.dig(:message) - - logger.debug( format( ' => %s', c ) ) - logger.debug( format( ' - [%s] %s', exit_code, exit_message ) ) - - if( exit_code != true ) - logger.error(exit_message) - logger.error(format(' command \'%s\'', c)) - logger.error(format(' returned with exit-code \'%s\'', exit_code)) - - return { status: 500, message: format('Internal Error: \'%s\' - \'cmd %s\'', exit_message, c) } - end - - if( exit_message =~ %r{/information\//} ) - # logger.debug( 'no ticket' ) - else - pki_ticket = exit_message.strip - next_command = next_command.gsub!('%PKI_TICKET%', pki_ticket) unless( next_command.nil? ) - end - end - - FileUtils.cp( pki_master_ca, format('%s/ca.crt', tmp_host_directory) ) - - # # TODO - # Build Checksum - # Dir[ sprintf( '%s/*', tmp_host_directory ) ].each do |file| - # if( File.directory?( file ) ) - # next - # end - # - # Digest::SHA2.hexdigest( File.read( file ) ) - # end - # - - # create TAR File - io = tar(tmp_host_directory) - # and compress - gz = gzip(io) - - # write to filesystem - - archive_name = format('%s/%s.tgz', @tmp_directory, host) - - begin - file = File.open(archive_name, 'w') - - file.binmode - file.write(gz.read) - rescue IOError => e - # some error occur, dir not writable etc. - logger.error(e) - ensure - file.close unless file.nil? - end - - checksum = Digest::SHA2.hexdigest(File.read(archive_name)) - timestamp = Time.now - timeout = timestamp.add_minutes(10) - - logger.debug(format(' timestamp : %s', timestamp.to_datetime.strftime('%d-%m-%Y %H:%M:%S'))) - logger.debug(format(' timeout : %s', timeout.to_datetime.strftime('%d-%m-%Y %H:%M:%S'))) - - # store datas in-memory - # - save(checksum, timestamp: timestamp, timeout: timeout, host: host) - - # remove the temporary data - # - FileUtils.rm_rf(tmp_host_directory) - - { - status: 200, - master_name: server_name, - master_ip: server_ip, - checksum: checksum, - timestamp: timestamp.to_i, - timeout: timeout.to_i, - file_name: format('%s.tgz', host), - path: @tmp_directory - } - end - - # create a icinga2 Ticket - # (NOT USED YET) - # - def create_ticket( params ) - - host = params.dig(:host) - - return { status: 500, message: 'no hostname to create a ticket' } if( host.nil? ) - - server_name = Socket.gethostbyname(Socket.gethostname).first - server_ip = IPSocket.getaddress(Socket.gethostname) - - # logger.debug(host) - - file_name = '/etc/icinga2/constants.conf' - - file = File.open(file_name, 'r') - contents = file.read - - regexp_long = / # Match she-bang style C-comment - \/\* # Opening delimiter. - [^*]*\*+ # {normal*} Zero or more non-*, one or more * - (?: # Begin {(special normal*)*} construct. - [^*\/] # {special} a non-*, non-\/ following star. - [^*]*\*+ # More {normal*} - )* # Finish "Unrolling-the-Loop" - \/ # Closing delimiter. - /x - result = contents.gsub(regexp_long, '') - -# logger.debug(result) - - ticket_salt = result.scan(/const TicketSalt(.*)=(.*)"(?.+\S)"/).flatten - host_ticket = nil - - if( ticket_salt.to_s != '' ) - logger.debug(format(' ticket Salt : %s', ticket_salt)) - else - o = [('a'..'z'), ('A'..'Z'), (0..9)].map(&:to_a).flatten - string = (0...50).map { o[rand(o.length)] }.join - - ticket_salt = Digest::SHA256.hexdigest(string) - - File.write(file_name, text.gsub(/const TicketSalt = ""/, "const TicketSalt = \"#{ticket_salt}\"")) - end - - commands = [] - commands << format('icinga2 pki ticket --cn %s --salt %s', host, ticket_salt) - - commands.each_with_index do |c, _index| - result = exec_command(cmd: c) - - exit_code = result.dig(:code) - exit_message = result.dig(:message) - - if exit_code != true - logger.error(format('command \'%s\'', c)) - logger.error(format('returned with exit-code %d', exit_code)) - logger.error(exit_message) - - abort 'FAILED !!!' - end - - host_ticket = exit_message -# logger.debug(host_ticket) - end - - timestamp = Time.now - - { - status: 200, - master_name: server_name, - master_ip: server_ip, - ticket: host_ticket, - timestamp: timestamp.to_i - } - end - - # check the certificate Data - # - # @param [Hash, #read] params - # @option params [String] :host - # @option params [Hash] :request - # - # @example - # check_certificate( host: 'icinga2-satellite', request: { 'HTTP_X_CHECKSUM' => '000000000000000000000000000000000000' } ) - # - # @return [Hash, #read] for an error: - # * :status [Integer] 404 or 500 - # * :message [String] Error Message - # - # @return [Hash, #read] for succesfuly: - # * :status [Integer] 200 - # * :file_name [String] Filename - # * :path [String] - # - def check_certificate( params ) - - host = params.dig(:host) - checksum = params.dig(:request, 'HTTP_X_CHECKSUM') - api_user = params.dig(:request, 'HTTP_X_API_USER') - api_password = params.dig(:request, 'HTTP_X_API_PASSWORD') - - return { status: 500, message: 'no valid data to get the certificate' } if( host.nil? || checksum.nil? ) - - file = format('%s/%s.tgz', @tmp_directory, host) - - return { status: 404, message: 'file doesn\'t exits' } unless( File.exist?(file) ) - - in_memory_data = find_by_id(checksum) - generated_timeout = in_memory_data.dig(:timeout) - generated_timeout = File.mtime(file).add_minutes(10) if( generated_timeout.nil? ) - - check_timestamp = Time.now - - return { status: 404, message: 'timed out. please ask for an new cert' } if( check_timestamp.to_i > generated_timeout.to_i ) - - # add params to create the endpoint not in zones.d - # - params[:satellite] = true - - # add API User for this Endpoint - # - # add_api_user(params) - - # add Endpoint (and API User) - # and create a backup of the generated files - # - add_endpoint(params) - - # restart service to activate the new certificate - # - # reload_icinga_config(params) - - { status: 200, file_name: format('%s.tgz', host), path: @tmp_directory } - end - - - # validate the CA against a checksum - # - # @param [Hash, #read] params - # @option params [String] :checksum - # - def validate_certificate( params ) - - checksum = params.dig(:checksum) - - return { status: 500, message: 'missing checksum' } if( checksum.nil? ) - - pki_base_directory = '/var/lib/icinga2/ca' - pki_master_ca = format('%s/ca.crt', pki_base_directory) - - return { status: 500, message: 'no PKI directory found. Please configure first the Icinga2 Master!' } unless( File.exist?(pki_base_directory) ) - - if( checksum.be_a_checksum ) - pki_master_ca_checksum = nil - pki_master_ca_checksum = Digest::MD5.hexdigest(File.read(pki_master_ca)) if( checksum.produced_by(:md5) ) - pki_master_ca_checksum = Digest::SHA256.hexdigest(File.read(pki_master_ca)) if( checksum.produced_by(:sha256) ) - pki_master_ca_checksum = Digest::SHA384.hexdigest(File.read(pki_master_ca)) if( checksum.produced_by(:sha384) ) - pki_master_ca_checksum = Digest::SHA512.hexdigest(File.read(pki_master_ca)) if( checksum.produced_by(:sha512) ) - - return { status: 500, message: 'wrong checksum type. only md5, sha256, sha384 and sha512 is supported' } if( pki_master_ca_checksum.nil? ) - return { status: 404, message: 'checksum not match.' } if( checksum != pki_master_ca_checksum ) - return { status: 200 } - end - - { status: 500, message: 'checksum isn\'t a checksum' } - end - - - # sign a icinga2 satellite certificate with the new 2.8 pki feature - # - # @param [Hash, #read] params - # @option params [String] :host - # @option params [Hash] :request - # - # @example - # sign_certificate( host: 'icinga2-satellite', request: { 'HTTP_X_API_USER => 'admin', HTTP_X_API_PASSWORD' => 'admin' } } ) - # - # @return [Hash, #read] for an error: - # * :status [Integer] 404 or 500 - # * :message [String] Error Message - # - # @return [Hash, #read] for succesfuly: - # * :status [Integer] 200 - # * :file_name [String] Filename - # * :path [String] - # - def sign_certificate( params ) - - host = params.dig(:host) - api_user = params.dig(:request, 'HTTP_X_API_USER') - api_password = params.dig(:request, 'HTTP_X_API_PASSWORD') - - return { status: 500, message: 'no hostname' } if( host.nil? ) - return { status: 500, message: 'missing API Credentials - API_USER' } if( api_user.nil?) - return { status: 500, message: 'missing API Credentials - API_PASSWORD' } if( api_password.nil? ) - - password = read_api_credentials( api_user: api_user ) - - return { status: 500, message: 'wrong API Credentials' } if( password.nil? || api_password != password ) - return { status: 500, message: 'wrong Icinga2 Version' } if( @icinga_version != '2.8' ) - - # /etc/icinga2 # icinga2 ca list | grep icinga2-satellite-1.matrix.lan | sort -k2 - # e39c0b4bab4d0d9d5f97f0f54da875f0a60273b4fa3d3ef5d9be0d379e7a058b | Jan 10 04:27:38 2018 GMT | * | CN = icinga2-satellite-1.matrix.lan - # 5520324447b124a26107ded6d5e5b37d73e2cf2074bd2b5e9d8b860939f490df | Jan 10 04:51:38 2018 GMT | | CN = icinga2-satellite-1.matrix.lan - # 6775ea210c7559cf58093dbb151de1aaa3635950f696165eb4beca28487d193c | Jan 10 05:03:36 2018 GMT | | CN = icinga2-satellite-1.matrix.lan - - commands = [] - commands << format('icinga2 ca list | grep %s | sort -k2 | tail -1', host) # sort by date - - commands.each_with_index do |c, index| - - result = exec_command(cmd: c) - exit_code = result.dig(:code) - exit_message = result.dig(:message) - - logger.debug( "icinga2 ca list: #{exit_message}" ) - logger.debug( "exit code: #{exit_code} (#{exit_code.class.to_s})" ) - - return { status: 500, message: 'error to retrive the list of certificates with signing requests' } if( exit_code == false ) - - regex = /^(?.+\S) \|(.*)\|(.*)\| CN = (?.+\S)$/ - parts = exit_message.match(regex) if(exit_message.is_a?(String)) - - logger.debug( "parts: #{parts} (#{parts.class.to_s})" ) - - if( parts ) - ticket = parts['ticket'].to_s.strip - cn = parts['cn'].to_s.strip - - result = exec_command(cmd: format('icinga2 ca sign %s',ticket)) - exit_code = result.dig(:code) - exit_message = result.dig(:message) - - logger.debug(exit_code) - logger.debug(exit_message) - - message = exit_message.gsub('information/cli: ','') - - logger.info(message) - - # create the endpoint and the reference zone - # the endpoint are only after an reload available! - # - add_endpoint(params) - - # logger.debug( format('set reload flag after creating the endpoint (%s)',host) ) - - # set an reload flag - # - # @cache.set( 'reload' , expires_in: 120 ) { MiniCache::Data.new( params ) } - - # reload the icinga configuration - # - #status = reload_icinga_config(params) - #logger.debug(status) - - return { status: 200, message: message } - - else - logger.error(format('i can\'t find a Ticket for host \'%s\'',host)) - logger.error( parts ) - - return { status: 404, message: format('i can\'t find a Ticket for host \'%s\'',host) } - end - end - - { status: 204 } - end - end -end diff --git a/build/ruby-icinga-cert-service/lib/cert-service/configure_icinga.rb b/build/ruby-icinga-cert-service/lib/cert-service/configure_icinga.rb deleted file mode 100644 index 8d1c8b6..0000000 --- a/build/ruby-icinga-cert-service/lib/cert-service/configure_icinga.rb +++ /dev/null @@ -1 +0,0 @@ - diff --git a/build/ruby-icinga-cert-service/lib/cert-service/endpoint_handler.rb b/build/ruby-icinga-cert-service/lib/cert-service/endpoint_handler.rb deleted file mode 100644 index ae0e002..0000000 --- a/build/ruby-icinga-cert-service/lib/cert-service/endpoint_handler.rb +++ /dev/null @@ -1,117 +0,0 @@ - -module IcingaCertService - # Client Class to create on-the-fly a certificate to connect automaticly as satellite to an icinga2-master - # - # - module EndpointHandler - - # add a Endpoint for distributed monitoring to the icinga2-master configuration - # - # @param [Hash, #read] params - # @option params [String] :host - # - # @example - # add_endpoint( host: 'icinga2-satellite' ) - # - # @return [Hash, #read] if config already created: - # * :status [Integer] 204 - # * :message [String] Message - # - # @return nil if successful - # - def add_endpoint(params) - - host = params.dig(:host) - satellite = params.dig(:satellite) || false - - return { status: 500, message: 'no hostname to create an endpoint' } if host.nil? - - # add the API user - # - add_api_user(params) - - # add the zone object - # - add_zone(host) - - if( satellite ) - file_name = '/etc/icinga2/zones.conf' - else - zone_directory = format('/etc/icinga2/zones.d/%s', host) - file_name = format('%s/%s.conf', zone_directory, host) - - FileUtils.mkpath(zone_directory) unless File.exist?(zone_directory) - end - - if( File.exist?(file_name) ) - - file = File.open(file_name, 'r') - contents = file.read - - regexp_long = / # Match she-bang style C-comment - \/\* # Opening delimiter. - [^*]*\*+ # {normal*} Zero or more non-*, one or more * - (?: # Begin {(special normal*)*} construct. - [^*\/] # {special} a non-*, non-\/ following star. - [^*]*\*+ # More {normal*} - )* # Finish "Unrolling-the-Loop" - \/ # Closing delimiter. - /x - result = contents.gsub(regexp_long, '') - - scan_endpoint = result.scan(/object Endpoint(.*)"(?.+\S)"/).flatten - - return { status: 200, message: format('the configuration for the endpoint %s already exists', host) } if( scan_endpoint.include?(host) == true ) - end - - logger.debug(format('i miss an configuration for endpoint %s', host)) - - File.open(file_name, 'a') do |f| - f << "/*\n" - f << " * generated at #{Time.now} with certificate service for Icinga2 #{IcingaCertService::VERSION}\n" - f << " */\n" - f << "object Endpoint \"#{host}\" {}\n" - end - - create_backup - - { status: 200, message: format('configuration for endpoint %s has been created', host) } - end - - end -end - - - -# object Host "icinga2-satellite-1.matrix.lan" { -# import "generic-host" -# check_command = "hostalive" -# address = "icinga2-satellite-1" -# -# vars.os = "Docker" -# vars.client_endpoint = name -# vars.satellite = true -# -# zone = "icinga2-satellite-1.matrix.lan" -# command_endpoint = "icinga2-satellite-1.matrix.lan" -# } -# -# apply Service "icinga" { -# /* import "generic-service" */ -# -# import "icinga-satellite-service" -# check_command = "icinga" -# -# /* zone = "icinga2-satellite-1.matrix.lan" -# command_endpoint = host.vars.client_endpoint */ -# -# assign where host.vars.satellite -# } -# -# -# zones.d/global-templates/templates_services.conf -# template Service "icinga-satellite-service" { -# import "generic-service" -# command_endpoint = host.vars.remote_endpoint -# zone = host.vars.remote_endpoint -# } diff --git a/build/ruby-icinga-cert-service/lib/cert-service/executor.rb b/build/ruby-icinga-cert-service/lib/cert-service/executor.rb deleted file mode 100644 index e3deb34..0000000 --- a/build/ruby-icinga-cert-service/lib/cert-service/executor.rb +++ /dev/null @@ -1,32 +0,0 @@ - -require 'open3' - -module IcingaCertService - - module Executor - - # execute system commands with a Open3.popen2() call - # - # @param [Hash, #read] params - # @option params [String] :cmd - # - # @return [Hash, #read] - # * :exit [Integer] Exit-Code - # * :message [String] Message - def exec_command( params ) - - cmd = params.dig(:cmd) - - return { code: 1, message: 'no command found' } if( cmd.nil? ) - - result = {} - - Open3.popen2( cmd ) do |_stdin, stdout_err, wait_thr| - return_value = wait_thr.value - result = { code: return_value.success?, message: stdout_err.gets } - end - - result - end - end -end diff --git a/build/ruby-icinga-cert-service/lib/cert-service/in-memory-cache.rb b/build/ruby-icinga-cert-service/lib/cert-service/in-memory-cache.rb deleted file mode 100644 index f2002bb..0000000 --- a/build/ruby-icinga-cert-service/lib/cert-service/in-memory-cache.rb +++ /dev/null @@ -1,41 +0,0 @@ - -module IcingaCertService - - # small in-memory Cache - # - module InMemoryDataCache - # create a new Instance - def initialize - @storage = {} - end - - # save data - # - # @param [String, #read] id - # @param [misc, #read] data - # - def save(id, data) - @storage ||= {} - @storage[id] ||= {} - @storage[id] = data - end - - # get data - # - # @param [String, #read] - # - def find_by_id(id) - if( !@storage.nil? ) - @storage.dig(id) || {} - else - {} - end - end - - # get all data - # - def entries - @storage - end - end -end diff --git a/build/ruby-icinga-cert-service/lib/cert-service/monkey.rb b/build/ruby-icinga-cert-service/lib/cert-service/monkey.rb deleted file mode 100644 index 7bdb4cd..0000000 --- a/build/ruby-icinga-cert-service/lib/cert-service/monkey.rb +++ /dev/null @@ -1,39 +0,0 @@ - -# check if is a checksum -# -class Object - - REGEX = /\A[0-9a-f]{32,128}\z/i - CHARS = { - md2: 32, - md4: 32, - md5: 32, - sha1: 40, - sha224: 56, - sha256: 64, - sha384: 96, - sha512: 128 - } - - def be_a_checksum - !!(self =~ REGEX) - end - - def produced_by( name ) - function = name.to_s.downcase.to_sym - - raise ArgumentError, "unknown algorithm given to be_a_checksum.produced_by: #{function}" unless CHARS.include?(function) - - return true if( size == CHARS[function] ) - false - end -end - -# add minutes -# -class Time - def add_minutes(m) - self + (60 * m) - end -end - diff --git a/build/ruby-icinga-cert-service/lib/cert-service/version.rb b/build/ruby-icinga-cert-service/lib/cert-service/version.rb deleted file mode 100644 index 3b77c34..0000000 --- a/build/ruby-icinga-cert-service/lib/cert-service/version.rb +++ /dev/null @@ -1,6 +0,0 @@ - -# frozen_string_literal: true - -module IcingaCertService - VERSION = '0.16.3'.freeze -end diff --git a/build/ruby-icinga-cert-service/lib/cert-service/zone_handler.rb b/build/ruby-icinga-cert-service/lib/cert-service/zone_handler.rb deleted file mode 100644 index 0190291..0000000 --- a/build/ruby-icinga-cert-service/lib/cert-service/zone_handler.rb +++ /dev/null @@ -1,63 +0,0 @@ - -module IcingaCertService - # Client Class to create on-the-fly a certificate to connect automaticly as satellite to an icinga2-master - # - # - module ZoneHandler - - # add a satellite zone to 'zones.conf' - # - # @param [String] zone - # - # @example - # add_zone('icinga2-satellite') - # - # @return [Hash, #read] if config already created: - # * :status [Integer] 200 - # * :message [String] Message - # - # @return nil if successful - # - def add_zone(zone) - - return { status: 500, message: 'no zone defined' } if zone.nil? - - zone_file = '/etc/icinga2/zones.conf' - - if(File.exist?(zone_file)) - - file = File.open(zone_file, 'r') - contents = file.read - - regexp_long = / # Match she-bang style C-comment - \/\* # Opening delimiter. - [^*]*\*+ # {normal*} Zero or more non-*, one or more * - (?: # Begin {(special normal*)*} construct. - [^*\/] # {special} a non-*, non-\/ following star. - [^*]*\*+ # More {normal*} - )* # Finish "Unrolling-the-Loop" - \/ # Closing delimiter. - /x - result = contents.gsub(regexp_long, '') - scan_zone = result.scan(/object Zone(.*)"(?.+\S)"/).flatten - - return { status: 200, message: format('the configuration for the zone %s already exists', zone) } if( scan_zone.include?(zone) == true ) - end - - logger.debug(format('i miss an configuration for zone %s', zone)) - - File.open(zone_file, 'a') do |f| - f << "/*\n" - f << " * generated at #{Time.now} with certificate service for Icinga2 #{IcingaCertService::VERSION}\n" - f << " */\n" - f << "object Zone \"#{zone}\" {\n" - f << " parent = \"#{@icinga_master}\"\n" - f << " endpoints = [ \"#{zone}\" ]\n" - f << "}\n\n" - end - - { status: 200, message: format('configuration for zone %s has been created', zone) } - end - - end -end diff --git a/build/ruby-icinga-cert-service/lib/logging.rb b/build/ruby-icinga-cert-service/lib/logging.rb deleted file mode 100644 index 1b66802..0000000 --- a/build/ruby-icinga-cert-service/lib/logging.rb +++ /dev/null @@ -1,37 +0,0 @@ - -require 'logger' - -# ------------------------------------------------------------------------------------------------- - -module Logging - - def logger - @logger ||= Logging.logger_for( self.class.name ) - end - - # Use a hash class-ivar to cache a unique Logger per class: - @loggers = {} - - class << self - - def logger_for( classname ) - @loggers[classname] ||= configure_logger_for( classname ) - end - - def configure_logger_for( classname ) - - $stdout.sync = true - logger = Logger.new($stdout) -# logger.progname = classname - logger.level = Logger::INFO - logger.datetime_format = '%Y-%m-%d %H:%M:%S::%3N' - logger.formatter = proc do |severity, datetime, progname, msg| - "[#{datetime.strftime( logger.datetime_format )}] #{severity.ljust(5)} : #{progname} #{msg}\n" - end - - logger - end - end -end - -# ------------------------------------------------------------------------------------------------- diff --git a/build/ruby-icinga-cert-service/lib/logging.rb~ b/build/ruby-icinga-cert-service/lib/logging.rb~ deleted file mode 100644 index c84072b..0000000 --- a/build/ruby-icinga-cert-service/lib/logging.rb~ +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/ruby - -require 'logger' - -# ------------------------------------------------------------------------------------------------- - -module Logging - - def logger - @logger ||= Logging.logger_for( self.class.name ) - end - - # Use a hash class-ivar to cache a unique Logger per class: - @loggers = {} - - class << self - def logger_for( classname ) - @loggers[classname] ||= configure_logger_for( classname ) - end - - def configure_logger_for( classname ) - - logFile = '/var/log/cert-service.log' - file = File.open( logFile, File::WRONLY | File::APPEND | File::CREAT ) - file.sync = true - logger = Logger.new( file, 'weekly', 1024000 ) - -# logger = Logger.new(STDOUT) - logger.progname = classname - logger.level = Logger::DEBUG - logger.datetime_format = "%Y-%m-%d %H:%M:%S::%3N" - logger.formatter = proc do |severity, datetime, progname, msg| - "[#{datetime.strftime( logger.datetime_format )}] #{severity.ljust(5)} : #{progname} - #{msg}\n" - end - - logger - end - end -end - -# ------------------------------------------------------------------------------------------------- diff --git a/build/ruby-icinga-cert-service/lib/util.rb b/build/ruby-icinga-cert-service/lib/util.rb deleted file mode 100644 index d5259dd..0000000 --- a/build/ruby-icinga-cert-service/lib/util.rb +++ /dev/null @@ -1,90 +0,0 @@ - -require 'rubygems' -require 'rubygems/package' - -require 'zlib' -require 'fileutils' - -module Util - module Tar - # Creates a tar file in memory recursively - # from the given path. - # - # Returns a StringIO whose underlying String - # is the contents of the tar file. - def tar(path) - tarfile = StringIO.new('') - Gem::Package::TarWriter.new(tarfile) do |tar| - Dir[File.join(path, '**/*')].each do |file| - mode = File.stat(file).mode - relative_file = file.sub /^#{Regexp.escape path}\/?/, '' - - if File.directory?(file) - tar.mkdir relative_file, mode - else - tar.add_file relative_file, mode do |tf| - File.open(file, 'rb') { |f| tf.write f.read } - end - end - end - end - - tarfile.rewind - tarfile - end - - # gzips the underlying string in the given StringIO, - # returning a new StringIO representing the - # compressed file. - def gzip(tarfile) - gz = StringIO.new('') - z = Zlib::GzipWriter.new(gz) - z.write tarfile.string - z.close # this is necessary! - - # z was closed to write the gzip footer, so - # now we need a new StringIO - StringIO.new gz.string - end - - # un-gzips the given IO, returning the - # decompressed version as a StringIO - def ungzip(tarfile) - z = Zlib::GzipReader.new(tarfile) - unzipped = StringIO.new(z.read) - z.close - unzipped - end - - # untars the given IO into the specified - # directory - def untar(io, destination) - Gem::Package::TarReader.new io do |tar| - tar.each do |tarfile| - destination_file = File.join destination, tarfile.full_name - - if tarfile.directory? - FileUtils.mkdir_p destination_file - else - destination_directory = File.dirname(destination_file) - FileUtils.mkdir_p destination_directory unless File.directory?(destination_directory) - File.open destination_file, 'wb' do |f| - f.print tarfile.read - end - end - end - end - end - end -end - -### Usage Example: ### -# -# include Util::Tar -# -# io = tar("./Desktop") # io is a TAR of files -# gz = gzip(io) # gz is a TGZ -# -# io = ungzip(gz) # io is a TAR -# untar(io, "./untarred") # files are untarred -# From c515427b8cdb22a78bc582319f0cf5d657f063ef Mon Sep 17 00:00:00 2001 From: Bodo Schulz Date: Wed, 17 Jan 2018 06:14:16 +0100 Subject: [PATCH 5/5] fix function name --- rootfs/init/common.sh | 2 +- rootfs/init/run.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rootfs/init/common.sh b/rootfs/init/common.sh index 0c134fd..a25f482 100644 --- a/rootfs/init/common.sh +++ b/rootfs/init/common.sh @@ -158,7 +158,7 @@ curl_opts() { } -validate_cert-service_environment() { +validate_certservice_environment() { ICINGA_CERT_SERVICE_BA_USER=${ICINGA_CERT_SERVICE_BA_USER:-"admin"} ICINGA_CERT_SERVICE_BA_PASSWORD=${ICINGA_CERT_SERVICE_BA_PASSWORD:-"admin"} diff --git a/rootfs/init/run.sh b/rootfs/init/run.sh index 2f0afb4..3f71546 100755 --- a/rootfs/init/run.sh +++ b/rootfs/init/run.sh @@ -76,7 +76,7 @@ run() { . /init/common.sh prepare - validate_cert-service_environment + validate_certservice_environment . /init/database/mysql.sh . /init/configure_icinga.sh