diff --git a/.fixtures.yml b/.fixtures.yml --- a/.fixtures.yml +++ b/.fixtures.yml @@ -8,7 +8,7 @@ ref: v10.2.0 postgresql: repo: https://github.com/puppetlabs/puppetlabs-postgresql.git - ref: v6.2.0 + ref: v6.4.0 java: repo: https://github.com/puppetlabs/puppetlabs-java.git ref: v5.0.0 diff --git a/.rubocop.yml b/.rubocop.yml --- a/.rubocop.yml +++ b/.rubocop.yml @@ -27,6 +27,7 @@ Description: We don't want to decorate test output. Exclude: - spec/**/* + Enabled: false RSpec/BeforeAfterAll: Description: Beware of using after(:all) as it may cause state to leak between tests. A necessary evil in acceptance testing. @@ -39,6 +40,10 @@ Description: Prefer braces for chaining. Mostly an aesthetical choice. Better to be consistent then. EnforcedStyle: braces_for_chaining +Style/BracesAroundHashParameters: + Description: Braces are required by Ruby 2.7. Cop removed from RuboCop v0.80.0. + See https://github.com/rubocop-hq/rubocop/pull/7643 + Enabled: true Style/ClassAndModuleChildren: Description: Compact style reduces the required amount of indentation. EnforcedStyle: compact @@ -88,6 +93,12 @@ Enabled: true Style/StringMethods: Enabled: true +GetText/DecorateFunctionMessage: + Enabled: false +GetText/DecorateStringFormattingUsingInterpolation: + Enabled: false +GetText/DecorateStringFormattingUsingPercent: + Enabled: false Layout/EndOfLine: Enabled: false Layout/IndentHeredoc: diff --git a/.sync.yml b/.sync.yml --- a/.sync.yml +++ b/.sync.yml @@ -23,7 +23,7 @@ - set: centos-7 collection: puppet5 extra_envs: - - BEAKER_keycloak_version: 9.0.0 + - BEAKER_keycloak_version: 10.0.1 - BEAKER_keycloak_full: yes - set: centos-7 collection: puppet6 @@ -32,56 +32,56 @@ - set: centos-7 collection: puppet6 extra_envs: - - BEAKER_keycloak_version: 9.0.0 + - BEAKER_keycloak_version: 10.0.1 - BEAKER_keycloak_full: yes - set: centos-8 collection: puppet5 - set: centos-8 collection: puppet5 extra_envs: - - BEAKER_keycloak_version: 9.0.0 + - BEAKER_keycloak_version: 10.0.1 - set: centos-8 collection: puppet6 - set: centos-8 collection: puppet6 extra_envs: - - BEAKER_keycloak_version: 9.0.0 + - BEAKER_keycloak_version: 10.0.1 - set: debian-9 collection: puppet5 - set: debian-9 collection: puppet5 extra_envs: - - BEAKER_keycloak_version: 9.0.0 + - BEAKER_keycloak_version: 10.0.1 - set: debian-9 collection: puppet6 - set: debian-9 collection: puppet6 extra_envs: - - BEAKER_keycloak_version: 9.0.0 + - BEAKER_keycloak_version: 10.0.1 - set: debian-10 collection: puppet5 - set: debian-10 collection: puppet5 extra_envs: - - BEAKER_keycloak_version: 9.0.0 + - BEAKER_keycloak_version: 10.0.1 - set: debian-10 collection: puppet6 - set: debian-10 collection: puppet6 extra_envs: - - BEAKER_keycloak_version: 9.0.0 + - BEAKER_keycloak_version: 10.0.1 - set: ubuntu-1804 collection: puppet5 - set: ubuntu-1804 collection: puppet5 extra_envs: - - BEAKER_keycloak_version: 9.0.0 + - BEAKER_keycloak_version: 10.0.1 - set: ubuntu-1804 collection: puppet6 - set: ubuntu-1804 collection: puppet6 extra_envs: - - BEAKER_keycloak_version: 9.0.0 + - BEAKER_keycloak_version: 10.0.1 user: treydock secure: "u4N3WOhiO4X0mmEF7I+ARGpcw4Wrmt1xA8cmG2Qlnvr+r3c7RgWfc6GoabLicGKoN/OqVPD1b6lXJ+Xrg8VAJb9NmFjXYkowbTYgyyPsx6fSIshaquThkrEUsaF1C5hWx1rADXCz8hkpvX537xye/uKQlvDjwHyHaJWu3rpCfsDApYwYZhIkKtsSk2hOlcX9jfI1LE/H6YYo44uRBxg2lyUusScJQcDe023mBYOOSet3C4w4UpPBqR0mu9XvjJHk0KJzBE2Jk6g7W+02/ZkVW9qDXh70mCE562uQN/CE8rjcM5V1M6L69YzG5rv0LSuV4rnrjNtkNz6GZPDKIpuwOLEkA0M+jBr+F2d4tPHyMVDGLQHIIl5/TxXU2A9+gDe9yZFeZ7KEOSkkYfEgKgHcPHMZQJhs7Xkj2ab+F3AFjrSbjWngX892NQXp9XK4EXBZzogdsEp+wHULc9ybb9BKUNS0FIbCOjJoqBuwe7Is8vfVQ+OXAxVnP1POEoAgmgD3gQVHtedBAYrT7Ge+uxILua+KaPYkxBh/Cg9TYYSJeO/y0LH8pV3zSOQ3oU3MGZRbZrLbNkFAq9sYu3Klw52NEgfgXNRaE5AIpC0Tjf/BHZuaGGPyZML4A14tctwzFCFmG8SXkN2fFtpS5LAfLzbizi2KecPMyEjSpHJATJ/6HuQ=" .gitlab-ci.yml: @@ -90,6 +90,8 @@ delete: true spec/acceptance/nodesets/centos-6.yml: delete: true +spec/acceptance/nodesets/centos-8.yml: + image: centos:8.1.1911 spec/acceptance/nodesets/debian-8.yml: delete: true spec/acceptance/nodesets/debian-10.yml: diff --git a/.travis.yml b/.travis.yml --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,25 @@ --- +os: linux dist: xenial language: ruby cache: bundler before_install: - bundle -v - rm -f Gemfile.lock - - gem update --system $RUBYGEMS_VERSION + - "# Update system gems if requested. This is useful to temporarily workaround troubles in the test runner" + - "# See https://github.com/puppetlabs/pdk-templates/commit/705154d5c437796b821691b707156e1b056d244f for an example of how this was used" + - "# Ignore exit code of SIGPIPE'd yes to not fail with shell's pipefail set" + - '[ -z "$RUBYGEMS_VERSION" ] || (yes || true) | gem update --system $RUBYGEMS_VERSION' - gem --version - bundle -v script: - 'bundle exec rake $CHECK' bundler_args: --without system_tests rvm: - - 2.5.3 + - 2.5.7 +env: + global: + - FACTER_GEM_VERSION="< 4.0" stages: - static - spec @@ -20,14 +27,14 @@ - if: tag =~ ^v\d name: deploy -matrix: +jobs: fast_finish: true include: - bundler_args: --with system_tests dist: xenial env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_set=centos-7 BEAKER_TESTMODE=apply BEAKER_keycloak_full=true - rvm: 2.5.3 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -35,8 +42,8 @@ - bundler_args: --with system_tests dist: xenial - env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_set=centos-7 BEAKER_TESTMODE=apply BEAKER_keycloak_version=9.0.0 BEAKER_keycloak_full=true - rvm: 2.5.3 + env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_set=centos-7 BEAKER_TESTMODE=apply BEAKER_keycloak_version=10.0.1 BEAKER_keycloak_full=true + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -45,7 +52,7 @@ bundler_args: --with system_tests dist: xenial env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_set=centos-7 BEAKER_TESTMODE=apply BEAKER_keycloak_full=true - rvm: 2.5.3 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -53,8 +60,8 @@ - bundler_args: --with system_tests dist: xenial - env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_set=centos-7 BEAKER_TESTMODE=apply BEAKER_keycloak_version=9.0.0 BEAKER_keycloak_full=true - rvm: 2.5.3 + env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_set=centos-7 BEAKER_TESTMODE=apply BEAKER_keycloak_version=10.0.1 BEAKER_keycloak_full=true + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -63,7 +70,7 @@ bundler_args: --with system_tests dist: xenial env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_set=centos-8 BEAKER_TESTMODE=apply - rvm: 2.5.3 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -71,8 +78,8 @@ - bundler_args: --with system_tests dist: xenial - env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_set=centos-8 BEAKER_TESTMODE=apply BEAKER_keycloak_version=9.0.0 - rvm: 2.5.3 + env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_set=centos-8 BEAKER_TESTMODE=apply BEAKER_keycloak_version=10.0.1 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -81,7 +88,7 @@ bundler_args: --with system_tests dist: xenial env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_set=centos-8 BEAKER_TESTMODE=apply - rvm: 2.5.3 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -89,8 +96,8 @@ - bundler_args: --with system_tests dist: xenial - env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_set=centos-8 BEAKER_TESTMODE=apply BEAKER_keycloak_version=9.0.0 - rvm: 2.5.3 + env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_set=centos-8 BEAKER_TESTMODE=apply BEAKER_keycloak_version=10.0.1 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -99,7 +106,7 @@ bundler_args: --with system_tests dist: xenial env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_set=debian-9 BEAKER_TESTMODE=apply - rvm: 2.5.3 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -107,8 +114,8 @@ - bundler_args: --with system_tests dist: xenial - env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_set=debian-9 BEAKER_TESTMODE=apply BEAKER_keycloak_version=9.0.0 - rvm: 2.5.3 + env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_set=debian-9 BEAKER_TESTMODE=apply BEAKER_keycloak_version=10.0.1 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -117,7 +124,7 @@ bundler_args: --with system_tests dist: xenial env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_set=debian-9 BEAKER_TESTMODE=apply - rvm: 2.5.3 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -125,8 +132,8 @@ - bundler_args: --with system_tests dist: xenial - env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_set=debian-9 BEAKER_TESTMODE=apply BEAKER_keycloak_version=9.0.0 - rvm: 2.5.3 + env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_set=debian-9 BEAKER_TESTMODE=apply BEAKER_keycloak_version=10.0.1 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -135,7 +142,7 @@ bundler_args: --with system_tests dist: xenial env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_set=debian-10 BEAKER_TESTMODE=apply - rvm: 2.5.3 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -143,8 +150,8 @@ - bundler_args: --with system_tests dist: xenial - env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_set=debian-10 BEAKER_TESTMODE=apply BEAKER_keycloak_version=9.0.0 - rvm: 2.5.3 + env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_set=debian-10 BEAKER_TESTMODE=apply BEAKER_keycloak_version=10.0.1 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -153,7 +160,7 @@ bundler_args: --with system_tests dist: xenial env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_set=debian-10 BEAKER_TESTMODE=apply - rvm: 2.5.3 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -161,8 +168,8 @@ - bundler_args: --with system_tests dist: xenial - env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_set=debian-10 BEAKER_TESTMODE=apply BEAKER_keycloak_version=9.0.0 - rvm: 2.5.3 + env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_set=debian-10 BEAKER_TESTMODE=apply BEAKER_keycloak_version=10.0.1 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -171,7 +178,7 @@ bundler_args: --with system_tests dist: xenial env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_set=ubuntu-1804 BEAKER_TESTMODE=apply - rvm: 2.5.3 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -179,8 +186,8 @@ - bundler_args: --with system_tests dist: xenial - env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_set=ubuntu-1804 BEAKER_TESTMODE=apply BEAKER_keycloak_version=9.0.0 - rvm: 2.5.3 + env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_set=ubuntu-1804 BEAKER_TESTMODE=apply BEAKER_keycloak_version=10.0.1 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -189,7 +196,7 @@ bundler_args: --with system_tests dist: xenial env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_set=ubuntu-1804 BEAKER_TESTMODE=apply - rvm: 2.5.3 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -197,8 +204,8 @@ - bundler_args: --with system_tests dist: xenial - env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_set=ubuntu-1804 BEAKER_TESTMODE=apply BEAKER_keycloak_version=9.0.0 - rvm: 2.5.3 + env: PUPPET_INSTALL_TYPE=agent BEAKER_debug=true BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_set=ubuntu-1804 BEAKER_TESTMODE=apply BEAKER_keycloak_version=10.0.1 + rvm: 2.5.7 script: bundle exec rake beaker services: docker stage: acceptance @@ -208,11 +215,11 @@ stage: static - env: PUPPET_GEM_VERSION="~> 5.0" CHECK=parallel_spec - rvm: 2.4.5 + rvm: 2.4.9 stage: spec - env: PUPPET_GEM_VERSION="~> 6.0" CHECK=parallel_spec - rvm: 2.5.3 + rvm: 2.5.7 stage: spec - env: DEPLOY_TO_FORGE=yes @@ -240,7 +247,7 @@ email: treydock@gmail.com deploy: provider: puppetforge - user: treydock + username: treydock password: secure: "u4N3WOhiO4X0mmEF7I+ARGpcw4Wrmt1xA8cmG2Qlnvr+r3c7RgWfc6GoabLicGKoN/OqVPD1b6lXJ+Xrg8VAJb9NmFjXYkowbTYgyyPsx6fSIshaquThkrEUsaF1C5hWx1rADXCz8hkpvX537xye/uKQlvDjwHyHaJWu3rpCfsDApYwYZhIkKtsSk2hOlcX9jfI1LE/H6YYo44uRBxg2lyUusScJQcDe023mBYOOSet3C4w4UpPBqR0mu9XvjJHk0KJzBE2Jk6g7W+02/ZkVW9qDXh70mCE562uQN/CE8rjcM5V1M6L69YzG5rv0LSuV4rnrjNtkNz6GZPDKIpuwOLEkA0M+jBr+F2d4tPHyMVDGLQHIIl5/TxXU2A9+gDe9yZFeZ7KEOSkkYfEgKgHcPHMZQJhs7Xkj2ab+F3AFjrSbjWngX892NQXp9XK4EXBZzogdsEp+wHULc9ybb9BKUNS0FIbCOjJoqBuwe7Is8vfVQ+OXAxVnP1POEoAgmgD3gQVHtedBAYrT7Ge+uxILua+KaPYkxBh/Cg9TYYSJeO/y0LH8pV3zSOQ3oU3MGZRbZrLbNkFAq9sYu3Klw52NEgfgXNRaE5AIpC0Tjf/BHZuaGGPyZML4A14tctwzFCFmG8SXkN2fFtpS5LAfLzbizi2KecPMyEjSpHJATJ/6HuQ=" on: diff --git a/CHANGELOG.md b/CHANGELOG.md --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,74 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org). +## [v6.16.0](https://github.com/treydock/puppet-module-keycloak/tree/v6.16.0) (2020-08-21) + +[Full Changelog](https://github.com/treydock/puppet-module-keycloak/compare/v6.15.0...v6.16.0) + +### Added + +- Added a parameter to control if the managed user is a system user [\#152](https://github.com/treydock/puppet-module-keycloak/pull/152) ([ZloeSabo](https://github.com/ZloeSabo)) + +## [v6.15.0](https://github.com/treydock/puppet-module-keycloak/tree/v6.15.0) (2020-08-14) + +[Full Changelog](https://github.com/treydock/puppet-module-keycloak/compare/v6.14.0...v6.15.0) + +### Added + +- add resources [\#151](https://github.com/treydock/puppet-module-keycloak/pull/151) ([aba-rechsteiner](https://github.com/aba-rechsteiner)) + +## [v6.14.0](https://github.com/treydock/puppet-module-keycloak/tree/v6.14.0) (2020-08-11) + +[Full Changelog](https://github.com/treydock/puppet-module-keycloak/compare/v6.13.1...v6.14.0) + +### Added + +- add proxy-address-forwarding for https-listener [\#149](https://github.com/treydock/puppet-module-keycloak/pull/149) ([aba-rechsteiner](https://github.com/aba-rechsteiner)) +- Add support for required actions [\#148](https://github.com/treydock/puppet-module-keycloak/pull/148) ([ZloeSabo](https://github.com/ZloeSabo)) + +## [v6.13.1](https://github.com/treydock/puppet-module-keycloak/tree/v6.13.1) (2020-08-03) + +[Full Changelog](https://github.com/treydock/puppet-module-keycloak/compare/v6.13.0...v6.13.1) + +### Fixed + +- Explicitly specifies what user to use with the admin generation script [\#146](https://github.com/treydock/puppet-module-keycloak/pull/146) ([ZloeSabo](https://github.com/ZloeSabo)) + +## [v6.13.0](https://github.com/treydock/puppet-module-keycloak/tree/v6.13.0) (2020-07-07) + +[Full Changelog](https://github.com/treydock/puppet-module-keycloak/compare/v6.12.0...v6.13.0) + +### Added + +- Concat custom code fragment to config.cli [\#145](https://github.com/treydock/puppet-module-keycloak/pull/145) ([danifr](https://github.com/danifr)) +- Update usage of deprecated function postgresql\_password [\#143](https://github.com/treydock/puppet-module-keycloak/pull/143) ([Karlinde](https://github.com/Karlinde)) + +## [v6.12.0](https://github.com/treydock/puppet-module-keycloak/tree/v6.12.0) (2020-07-02) + +[Full Changelog](https://github.com/treydock/puppet-module-keycloak/compare/v6.11.0...v6.12.0) + +### Added + +- Emit warning if configured theme does not exist [\#140](https://github.com/treydock/puppet-module-keycloak/pull/140) ([treydock](https://github.com/treydock)) +- Add support for JGroups JDBC\_PING mode in clustered mode [\#139](https://github.com/treydock/puppet-module-keycloak/pull/139) ([danifr](https://github.com/danifr)) + +### UNCATEGORIZED PRS; GO LABEL THEM + +- Remove outdated line in class documentation [\#137](https://github.com/treydock/puppet-module-keycloak/pull/137) ([danifr](https://github.com/danifr)) + +## [v6.11.0](https://github.com/treydock/puppet-module-keycloak/tree/v6.11.0) (2020-05-22) + +[Full Changelog](https://github.com/treydock/puppet-module-keycloak/compare/v6.10.0...v6.11.0) + +### Added + +- PDK update and test Keycloak 10.0.1 [\#133](https://github.com/treydock/puppet-module-keycloak/pull/133) ([treydock](https://github.com/treydock)) + +### UNCATEGORIZED PRS; GO LABEL THEM + +- Add support for defining smtpServer from realms [\#131](https://github.com/treydock/puppet-module-keycloak/pull/131) ([mattock](https://github.com/mattock)) +- Allow enabling/disabling client authorization services [\#127](https://github.com/treydock/puppet-module-keycloak/pull/127) ([mattock](https://github.com/mattock)) + ## [v6.10.0](https://github.com/treydock/puppet-module-keycloak/tree/v6.10.0) (2020-03-14) [Full Changelog](https://github.com/treydock/puppet-module-keycloak/compare/v6.9.0...v6.10.0) @@ -276,7 +344,6 @@ ### Added - Use hiera v5 module data [\#62](https://github.com/treydock/puppet-module-keycloak/pull/62) ([treydock](https://github.com/treydock)) -- Expand postgresql support to behave more like mysql support, simplified a bit [\#60](https://github.com/treydock/puppet-module-keycloak/pull/60) ([treydock](https://github.com/treydock)) ## [v3.8.0](https://github.com/treydock/puppet-module-keycloak/tree/v3.8.0) (2019-05-23) @@ -284,6 +351,7 @@ ### Added +- Expand postgresql support to behave more like mysql support, simplified a bit [\#60](https://github.com/treydock/puppet-module-keycloak/pull/60) ([treydock](https://github.com/treydock)) - Use PDK [\#58](https://github.com/treydock/puppet-module-keycloak/pull/58) ([treydock](https://github.com/treydock)) ## [3.7.0](https://github.com/treydock/puppet-module-keycloak/tree/3.7.0) (2019-05-20) @@ -326,6 +394,7 @@ ### Added - JAVA\_OPTS via systemd unit Environment variable [\#51](https://github.com/treydock/puppet-module-keycloak/pull/51) ([danifr](https://github.com/danifr)) +- Add option for service environment file [\#50](https://github.com/treydock/puppet-module-keycloak/pull/50) ([asieraguado](https://github.com/asieraguado)) ## [3.3.0](https://github.com/treydock/puppet-module-keycloak/tree/3.3.0) (2019-01-28) @@ -333,7 +402,6 @@ ### Added -- Add option for service environment file [\#50](https://github.com/treydock/puppet-module-keycloak/pull/50) ([asieraguado](https://github.com/asieraguado)) - Better ID handling [\#47](https://github.com/treydock/puppet-module-keycloak/pull/47) ([treydock](https://github.com/treydock)) - Test against Keycloak 4.8.1.Final and document version handling and upgrade [\#43](https://github.com/treydock/puppet-module-keycloak/pull/43) ([treydock](https://github.com/treydock)) @@ -377,6 +445,10 @@ [Full Changelog](https://github.com/treydock/puppet-module-keycloak/compare/2.7.0...2.7.1) +### Fixed + +- Update reference [\#36](https://github.com/treydock/puppet-module-keycloak/pull/36) ([treydock](https://github.com/treydock)) + ## [2.7.0](https://github.com/treydock/puppet-module-keycloak/tree/2.7.0) (2018-08-14) [Full Changelog](https://github.com/treydock/puppet-module-keycloak/compare/2.6.0...2.7.0) @@ -395,6 +467,7 @@ - Add search\_scope and custom\_user\_search\_filter properties to keycloak\_ldap\_user\_provider type [\#29](https://github.com/treydock/puppet-module-keycloak/pull/29) ([treydock](https://github.com/treydock)) - Explicitly define all type properties [\#27](https://github.com/treydock/puppet-module-keycloak/pull/27) ([treydock](https://github.com/treydock)) - Improve acceptance tests [\#26](https://github.com/treydock/puppet-module-keycloak/pull/26) ([treydock](https://github.com/treydock)) +- Add keycloak\_api configuration type [\#22](https://github.com/treydock/puppet-module-keycloak/pull/22) ([treydock](https://github.com/treydock)) ### Fixed @@ -412,10 +485,6 @@ [Full Changelog](https://github.com/treydock/puppet-module-keycloak/compare/2.3.1...2.4.0) -### Added - -- Add keycloak\_api configuration type [\#22](https://github.com/treydock/puppet-module-keycloak/pull/22) ([treydock](https://github.com/treydock)) - ## [2.3.1](https://github.com/treydock/puppet-module-keycloak/tree/2.3.1) (2018-03-10) [Full Changelog](https://github.com/treydock/puppet-module-keycloak/compare/2.3.0...2.3.1) diff --git a/Gemfile b/Gemfile --- a/Gemfile +++ b/Gemfile @@ -17,6 +17,7 @@ minor_version = ruby_version_segments[0..1].join('.') group :development do + gem "facter", '< 4.0', require: false gem "fast_gettext", '1.1.0', require: false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.1.0') gem "fast_gettext", require: false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.1.0') gem "json_pure", '<= 2.0.1', require: false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0') @@ -24,38 +25,10 @@ gem "json", '= 2.0.4', require: false if Gem::Requirement.create('~> 2.4.2').satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) gem "json", '= 2.1.0', require: false if Gem::Requirement.create(['>= 2.5.0', '< 2.7.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) gem "rb-readline", '= 0.5.5', require: false, platforms: [:mswin, :mingw, :x64_mingw] - gem "puppet-module-posix-default-r#{minor_version}", '~> 0.3', require: false, platforms: [:ruby] - gem "activesupport", '~> 5.0', require: false - gem "codecov", '~> 0.1.10', require: false - gem "dependency_checker", '~> 0.2', require: false - gem "facterdb", '~> 0.8.1', require: false - gem "gettext-setup", '~> 0.26', require: false - gem "metadata-json-lint", '~> 2.0', require: false - gem "mocha", '~> 1.0', require: false - gem "parallel_tests", '~> 2.14.1', require: false - gem "parser", '~> 2.5.1.2', require: false - gem "pry", '~> 0.10.4', require: false - gem "puppet-debugger", '~> 0.14', require: false - gem "puppet-lint", '~> 2.3', require: false - gem "puppet_pot_generator", '~> 1.0', require: false - gem "puppet-strings", '~> 2.0', require: false - gem "puppet-resource_api", '~> 1.6', require: false - gem "puppet-syntax", '~> 2.4', require: false - gem "puppetlabs_spec_helper", '~> 2.9', require: false - gem "rainbow", '~> 2.0', require: false - gem "rspec-puppet", '~> 2.3', require: false - gem "rspec-puppet-facts", '~> 1.9.5', require: false - gem "rubocop", '~> 0.49.0', require: false - gem "rubocop-i18n", '~> 1.2.0', require: false - gem "rubocop-rspec", '~> 1.16.0', require: false - gem "rspec_junit_formatter", '~> 0.2', require: false - gem "serverspec", '~> 2.41', require: false - gem "simplecov-console", '~> 0.4.2', require: false - gem "specinfra", '2.82.2', require: false - gem "simplecov", '~> 0.14.1', require: false - gem "puppet-blacksmith", '>= 3.4.0', require: false - gem "puppet-module-win-default-r#{minor_version}", '~> 0.3', require: false, platforms: [:mswin, :mingw, :x64_mingw] - gem "puppet-module-win-dev-r#{minor_version}", '~> 0.3', require: false, platforms: [:mswin, :mingw, :x64_mingw] + gem "puppet-module-posix-default-r#{minor_version}", '~> 0.4', require: false, platforms: [:ruby] + gem "puppet-module-posix-dev-r#{minor_version}", '~> 0.4', require: false, platforms: [:ruby] + gem "puppet-module-win-default-r#{minor_version}", '~> 0.4', require: false, platforms: [:mswin, :mingw, :x64_mingw] + gem "puppet-module-win-dev-r#{minor_version}", '~> 0.4', require: false, platforms: [:mswin, :mingw, :x64_mingw] gem "puppet-lint-param-docs", require: false gem "github_changelog_generator", require: false, git: 'https://github.com/skywinder/github-changelog-generator', ref: '20ee04ba1234e9e83eb2ffb5056e23d641c7a018' if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.2.2') end @@ -67,7 +40,7 @@ gem "beaker-pe", require: false gem "beaker-hostgenerator" gem "beaker-rspec" - gem "beaker-docker", git: 'https://github.com/treydock/beaker-docker', ref: 'c6d1d3dc5f1e8b7770109793d15cfc9927eb1961' + gem "beaker-docker" gem "beaker-puppet" end diff --git a/README.md b/README.md --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ * [keycloak_identity_provider](#keycloak_identity_provider) * [Keycloak Flows](#keycloak-flows) * [keycloak_api](#keycloak_api) + * [keycloak_required_action](#keycloak_required_action) 3. [Reference - Parameter and detailed reference to all options](#reference) 4. [Limitations - OS compatibility, etc.](#limitations) @@ -147,7 +148,7 @@ } ``` -Run Keycloak using standalone clustered mode: +Run Keycloak using standalone clustered mode (multicast): ```puppet class { 'keycloak': @@ -155,6 +156,24 @@ } ``` +Run Keycloak using standalone clustered mode (JDBC_PING): + +> [JDBC_PING](http://jgroups.org/manual/#_jdbc_ping) uses port **7600** to ensure cluster members are discoverable by each other. This module **does NOT manage firewall changes**. + +```puppet +class { 'keycloak': + operating_mode => 'clustered', + datasource_driver => 'postgresql', + enable_jdbc_ping => true, + jboss_bind_private_address => $facts['networking']['ip'], + jboss_bind_public_address => $facts['networking']['ip'], +} + +# your puppet code to open port 7600 +# ... +# ... +``` + ### Deploy SPI A simple example of deploying a custom SPI from a URL: @@ -396,6 +415,38 @@ The path for `install_dir` will be joined with `bin/kcadm.sh` to produce the full path to `kcadm.sh`. +### keycloak\_required\_action + +The keycloak_required_action type can be used to define actions a user must perform during the authentication process. +A user will not be able to complete the authentication process until these actions are complete. For instance, change a one-time password, accept T&C, etc. + +The name for an action is `$alias on $realm`. + +**Important**: actions from puppet config and from a server are matched based on a combination of alias and realm, so edition of aliases is not supported. + + ```puppet +# Minimal example +keycloak_required_action { 'VERIFY_EMAIL on master': + ensure => present, + provider_id => 'webauthn-register', +} + +# Full example + +keycloak_required_action { 'webauthn-register on master': + ensure => present, + provider_id => 'webauthn-register', + display_name => 'Webauthn Register', + default => true, + enabled => true, + priority => 1, + config => { + 'something' => 'true', # keep in mind that keycloak only supports strings for both keys and values + 'smth else' => '1', + }, +} +``` + ## Reference [http://treydock.github.io/puppet-module-keycloak/](http://treydock.github.io/puppet-module-keycloak/) diff --git a/REFERENCE.md b/REFERENCE.md --- a/REFERENCE.md +++ b/REFERENCE.md @@ -42,6 +42,7 @@ * [`keycloak_ldap_user_provider`](#keycloak_ldap_user_provider): Manage Keycloak LDAP user providers * [`keycloak_protocol_mapper`](#keycloak_protocol_mapper): Manage Keycloak client scope protocol mappers * [`keycloak_realm`](#keycloak_realm): Manage Keycloak realms +* [`keycloak_required_action`](#keycloak_required_action): Manage Keycloak required actions * [`keycloak_resource_validator`](#keycloak_resource_validator): Verify that a specific Keycloak resource is available * [`keycloak_sssd_user_provider`](#keycloak_sssd_user_provider): Manage Keycloak SSSD user providers @@ -230,6 +231,15 @@ Default value: `undef` +##### `system_user` + +Data type: `Boolean` + +If keycloak user should be a system user with lower uid and gid. +Default is `true` + +Default value: `true` + ##### `admin_user` Data type: `String` @@ -253,7 +263,6 @@ Data type: `Boolean` Boolean that determines if configured datasource will be managed. -Only applies when `datasource_driver` is `mysql`. Default is `true`. Default value: `true` @@ -587,6 +596,54 @@ Default value: `false` +##### `required_actions` + +Data type: `Hash` + +Hash that is used to define keycloak_required_action resources. + +Default value: {} + +##### `required_actions_merge` + +Data type: `Boolean` + +Boolean that sets if `required_actions` should be merged from Hiera. + +Default value: `false` + +##### `ldap_mappers` + +Data type: `Hash` + +Hash that is used to define keycloak_ldap_mapper resources. + +Default value: {} + +##### `ldap_mappers_merge` + +Data type: `Boolean` + +Boolean that sets if `ldap_mappers` should be merged from Hiera. + +Default value: `false` + +##### `ldap_user_providers` + +Data type: `Hash` + +Hash that is used to define keycloak_ldap_user_provider resources. + +Default value: {} + +##### `ldap_user_providers_merge` + +Data type: `Boolean` + +Boolean that sets if `ldap_user_providers` should be merged from Hiera. + +Default value: `false` + ##### `with_sssd_support` Data type: `Boolean` @@ -675,6 +732,34 @@ Default value: 'standalone' +##### `enable_jdbc_ping` + +Data type: `Boolean` + +Use JDBC_PING to discover the nodes and manage the replication of data + More info: http://jgroups.org/manual/#_jdbc_ping +Only applies when `operating_mode` is `clustered` +JDBC_PING uses port 7600 to ensure cluster members are discoverable by each other +This module does not manage firewall changes + +Default value: `false` + +##### `jboss_bind_public_address` + +Data type: `Stdlib::IP::Address` + +JBoss bind public IP address + +Default value: $facts['networking']['ip'] + +##### `jboss_bind_private_address` + +Data type: `Stdlib::IP::Address` + +JBoss bind private IP address + +Default value: $facts['networking']['ip'] + ##### `user_cache` Data type: `Boolean` @@ -715,6 +800,22 @@ Default value: {} +##### `custom_config_content` + +Data type: `Optional[String]` + +Custom configuration content to be added to config.cli + +Default value: `undef` + +##### `custom_config_source` + +Data type: `Optional[Variant[String, Array]]` + +Custom configuration source file to be added to config.cli + +Default value: `undef` + ### keycloak::config Private class. @@ -1095,6 +1196,14 @@ Default value: false +##### `authorization_services_enabled` + +Valid values: `true`, `false` + +authorizationServicesEnabled + +Default value: false + ##### `public_client` Valid values: `true`, `false` @@ -2592,6 +2701,60 @@ Default value: false +##### `smtp_server_user` + +smtpServer user + +##### `smtp_server_password` + +smtpServer password + +##### `smtp_server_host` + +smtpServer host + +##### `smtp_server_port` + +smtpServer port + +##### `smtp_server_auth` + +Valid values: `true`, `false` + +smtpServer auth + +##### `smtp_server_starttls` + +Valid values: `true`, `false` + +smtpServer starttls + +##### `smtp_server_ssl` + +Valid values: `true`, `false` + +smtpServer ssl + +##### `smtp_server_from` + +smtpServer from + +##### `smtp_server_envelope_from` + +smtpServer envelope_from + +##### `smtp_server_from_display_name` + +smtpServer fromDisplayName + +##### `smtp_server_reply_to` + +smtpServer replyto + +##### `smtp_server_reply_to_display_name` + +smtpServer replyToDisplayName + #### Parameters The following parameters are available in the `keycloak_realm` type. @@ -2606,6 +2769,98 @@ Id. Default to `name`. +### keycloak_required_action + +Manage Keycloak required actions + +#### Examples + +##### Enable Webauthn Register and make it default + +```puppet +keycloak_required_action { 'webauthn-register on master': + ensure => present, + provider_id => 'webauthn-register', + display_name => 'Webauthn Register', + default => true, + enabled => true, + priority => 1, + config => { + 'something' => 'true', # keep in mind that keycloak only supports strings for both keys and values + 'smth else' => '1', + }, + alias => 'webauthn', +} + +@example Minimal example to enable email verification without making it default +keycloak_required_action { 'VERIFY_EMAIL on master': + ensure => present, + provider_id => 'webauthn-register', +} +``` + +#### Properties + +The following properties are available in the `keycloak_required_action` type. + +##### `ensure` + +Valid values: present, absent + +The basic property that the resource should be in. + +Default value: present + +##### `display_name` + +Displayed name. Default to `provider_id` + +##### `enabled` + +Valid values: `true`, `false` + +If the required action is enabled. Default to true. + +Default value: true + +##### `alias` + +Alias. Default to `provider_id`. + +##### `default` + +Valid values: `true`, `false` + +If the required action is a default one. Default to false + +Default value: false + +##### `priority` + +Required action priority + +##### `config` + +Required action config + +#### Parameters + +The following parameters are available in the `keycloak_required_action` type. + +##### `name` + +namevar + +The required action name + +##### `realm` + +realm + +##### `provider_id` + +providerId of the required action + ### keycloak_resource_validator Verify that a specific Keycloak resource is available diff --git a/Rakefile b/Rakefile --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'puppet_litmus/rake_tasks' if Bundler.rubygems.find_name('puppet_litmus').any? require 'puppetlabs_spec_helper/rake_tasks' require 'puppet-syntax/tasks/puppet-syntax' diff --git a/lib/puppet/provider/keycloak_api.rb b/lib/puppet/provider/keycloak_api.rb --- a/lib/puppet/provider/keycloak_api.rb +++ b/lib/puppet/provider/keycloak_api.rb @@ -155,4 +155,11 @@ def name_uuid(*args) self.class.name_uuid(*args) end + + def check_theme_exists(theme, res) + install_dir = self.class.install_dir || '/opt/keycloak' + path = File.join(install_dir, 'themes', theme) + return if File.exist?(path) + Puppet.warning("#{res}: Theme #{theme} not found at path #{path}.") + end end diff --git a/lib/puppet/provider/keycloak_client/kcadm.rb b/lib/puppet/provider/keycloak_client/kcadm.rb --- a/lib/puppet/provider/keycloak_client/kcadm.rb +++ b/lib/puppet/provider/keycloak_client/kcadm.rb @@ -140,6 +140,9 @@ t.close Puppet.debug(IO.read(t.path)) begin + if resource[:login_theme] + check_theme_exists(resource[:login_theme], "Keycloak_client[#{resource[:name]}]") + end output = kcadm('create', 'clients', resource[:realm], t.path) Puppet.debug("create client output: #{output}") rescue Puppet::ExecutionFailure => e @@ -243,6 +246,12 @@ end end + # Keycload API requires "serviceAccountsEnabled": true to be present in + # the JSON when "authorizationServicesEnabled": true + if data['authorizationServicesEnabled'] && data['serviceAccountsEnabled'].nil? + data[:serviceAccountsEnabled] = true + end + # Only update if more than clientId set if data.keys.size > 1 t = Tempfile.new('keycloak_client') @@ -250,6 +259,9 @@ t.close Puppet.debug(IO.read(t.path)) begin + if @property_flush[:login_theme] + check_theme_exists(@property_flush[:login_theme], "Keycloak_client[#{resource[:name]}]") + end kcadm('update', "clients/#{id}", resource[:realm], t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update client failed\nError message: #{e.message}" diff --git a/lib/puppet/provider/keycloak_realm/kcadm.rb b/lib/puppet/provider/keycloak_realm/kcadm.rb --- a/lib/puppet/provider/keycloak_realm/kcadm.rb +++ b/lib/puppet/provider/keycloak_realm/kcadm.rb @@ -16,6 +16,23 @@ ] end + def self.smtp_server_properties + [ + :smtp_server_user, + :smtp_server_password, + :smtp_server_host, + :smtp_server_port, + :smtp_server_auth, + :smtp_server_starttls, + :smtp_server_ssl, + :smtp_server_envelope_from, + :smtp_server_from, + :smtp_server_from_display_name, + :smtp_server_reply_to, + :smtp_server_reply_to_display_name, + ] + end + def self.browser_security_headers [ :content_security_policy, @@ -85,6 +102,8 @@ events_config[camelize(property)] elsif browser_security_headers.include?(property) d['browserSecurityHeaders'][camelize(property)] + elsif smtp_server_properties.include?(property) + d['smtpServer'][camelize(property.to_s.gsub(%r{smtp_server_}, ''))] else d[camelize(property)] end @@ -123,11 +142,16 @@ if self.class.browser_security_headers.include?(property) && !data.key?('browserSecurityHeaders') data['browserSecurityHeaders'] = {} end + if self.class.smtp_server_properties.include?(property) && !data.key?('smtpServer') + data['smtpServer'] = {} + end if property.to_s =~ %r{events} events_config[camelize(property)] = convert_property_value(resource[property.to_sym]) elsif resource[property.to_sym] if self.class.browser_security_headers.include?(property) data['browserSecurityHeaders'][camelize(property)] = convert_property_value(resource[property.to_sym]) + elsif self.class.smtp_server_properties.include?(property) && resource[property] + data['smtpServer'][camelize(property.to_s.gsub(%r{smtp_server_}, ''))] = resource[property] else data[camelize(property)] = convert_property_value(resource[property.to_sym]) end @@ -139,6 +163,16 @@ t.close Puppet.debug(IO.read(t.path)) begin + [ + :login_theme, + :account_theme, + :admin_theme, + :email_theme, + ].each do |theme| + if resource[theme] + check_theme_exists(resource[theme], "Keycloak_realm[#{resource[:name]}]") + end + end kcadm('create', 'realms', nil, t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm create realm failed\nError message: #{e.message}" @@ -244,9 +278,14 @@ if self.class.browser_security_headers.include?(property) && !data.key?('browserSecurityHeaders') data['browserSecurityHeaders'] = {} end - if @property_flush[property.to_sym] # || resource[property.to_sym] + if self.class.smtp_server_properties.include?(property) && !data.key?('smtpServer') + data['smtpServer'] = {} + end + if @property_flush[property.to_sym] || resource[property.to_sym] if self.class.browser_security_headers.include?(property) data['browserSecurityHeaders'][camelize(property)] = convert_property_value(resource[property.to_sym]) + elsif self.class.smtp_server_properties.include?(property) && resource[property] + data['smtpServer'][camelize(property.to_s.gsub(%r{smtp_server_}, ''))] = resource[property] else data[camelize(property)] = convert_property_value(resource[property.to_sym]) end @@ -262,6 +301,16 @@ t.close Puppet.debug(IO.read(t.path)) begin + [ + :login_theme, + :account_theme, + :admin_theme, + :email_theme, + ].each do |theme| + if @property_flush[theme] + check_theme_exists(@property_flush[theme], "Keycloak_realm[#{resource[:name]}]") + end + end kcadm('update', "realms/#{resource[:name]}", nil, t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realm failed\nError message: #{e.message}" diff --git a/lib/puppet/provider/keycloak_required_action/kcadm.rb b/lib/puppet/provider/keycloak_required_action/kcadm.rb new file mode 100644 --- /dev/null +++ b/lib/puppet/provider/keycloak_required_action/kcadm.rb @@ -0,0 +1,161 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'keycloak_api')) + +Puppet::Type.type(:keycloak_required_action).provide(:kcadm, parent: Puppet::Provider::KeycloakAPI) do + desc '' + + mk_resource_methods + + def self.prefetch(resources) + action_providers = instances + resources.keys.each do |name| + provider = action_providers.find do |c| + c.alias == resources[name][:alias] && c.realm == resources[name][:realm] + end + if provider + resources[name].provider = provider + end + end + end + + def self.instances + action_instances = [] + realms.each do |realm| + output = kcadm('get', 'authentication/required-actions', realm) + Puppet.debug("#{realm} required-actions: #{output}") + begin + required_actions = JSON.parse(output) + rescue JSON::ParserError + Puppet.debug('Unable to parse output from kcadm get required-actions') + required_actions = [] + end + + required_actions.each do |a| + action = { + ensure: :present, + alias: a['alias'], + display_name: a['name'], + realm: realm, + enabled: a['enabled'], + provider_id: a['providerId'], + name: "#{a['providerId']} on #{realm}", + priority: a['priority'], + config: a['config'], + default: a['defaultAction'], + } + + Puppet.debug("Keycloak REQUIRED ACTION: #{action}") + action_instances << new(action) + end + + output = kcadm('get', 'authentication/unregistered-required-actions', realm) + Puppet.debug("#{realm} unregistered-required-actions: #{output}") + begin + unregistered_actions = JSON.parse(output) + rescue JSON::ParserError + Puppet.debug('Unable to parse output from kcadm get unregistered-required-actions') + unregistered_actions = [] + end + + unregistered_actions.each do |a| + action = { + ensure: :absent, + alias: a['providerId'], + display_name: a['name'], + realm: realm, + enabled: false, + default: false, + provider_id: a['providerId'], + name: "#{a['providerId']} on #{realm}", + } + + Puppet.debug("Keycloak UNREGISTERED REQUIRED ACTION: #{action}") + action_instances << new(action) + end + end + action_instances + end + + def initialize(value = {}) + super(value) + @property_flush = {} + end + + type_properties.each do |prop| + define_method "#{prop}=".to_sym do |value| + @property_flush[prop] = value + end + end + + def create + Puppet.debug('Keycloak required action: create') + + t = Tempfile.new('keycloak_required_action_register') + t.write(JSON.pretty_generate(providerId: resource[:provider_id], name: resource[:display_name])) + t.close + Puppet.debug(IO.read(t.path)) + begin + kcadm('create', 'authentication/register-required-action', resource[:realm], t.path) + rescue => e + raise Puppet::Error, "kcadm registration of required action failed\nError message: #{e.message}" + end + Puppet.info("Keycloak: registered required action for provider #{resource[:provider_id]} for #{resource[:realm]}") + + # Asigning property_flush to is needed to make the flush method to + # configure properties of the required action after the registration. + @property_flush = resource.to_hash + @property_hash[:alias] = resource[:provider_id] # Initially it's equal to the provider id until configuration is applied to it + @property_hash[:ensure] = :present + end + + def destroy + Puppet.debug('Keycloak required action: destroy') + begin + kcadm('delete', "authentication/required-actions/#{@property_hash[:alias]}", resource[:realm]) + rescue => e + raise Puppet::Error, "kcadm deletion of required action failed\nError message: #{e.message}" + end + Puppet.info("Keycloak: deregistered required action #{@property_hash[:alias]} for #{resource[:realm]}") + @property_hash.clear + end + + def exists? + !(@property_hash[:ensure] == :absent || @property_hash.empty?) + end + + def flush + Puppet.debug("Keycloak property_flush: #{@property_flush}") + return if @property_flush.empty? + + begin + t = Tempfile.new('keycloak_required_action_configure') + t.write(JSON.pretty_generate(alias: resource[:alias], + name: resource[:display_name] || @property_hash[:display_name], + enabled: resource[:enabled], + priority: resource[:priority], + config: resource[:config] || {}, + defaultAction: resource[:default])) + t.close + Puppet.debug(IO.read(t.path)) + kcadm('update', "authentication/required-actions/#{@property_hash[:alias]}", resource[:realm], t.path) + Puppet.info("Keycloak: configured required action #{@property_hash[:alias]} (provider #{resource[:provider_id]}) for #{resource[:realm]}") + rescue => e + raise Puppet::Error, "kcadm configuration of required action failed\nError message: #{e.message}" + end + + @property_flush.clear + @property_hash = resource.to_hash + end + + def to_keycloak_representation(resource) + { + alias: resource[:alias], + name: resource[:display_name], + realm: resource[:realm], + providerId: resource[:provider_id], + enabled: resource[:ensure] == :present, + priority: resource[:priority], + config: resource[:config], + defaultAction: resource[:default], + } + end +end diff --git a/lib/puppet/type/keycloak_client.rb b/lib/puppet/type/keycloak_client.rb --- a/lib/puppet/type/keycloak_client.rb +++ b/lib/puppet/type/keycloak_client.rb @@ -122,6 +122,26 @@ defaultto :false end + newproperty(:authorization_services_enabled, boolean: true) do + desc 'authorizationServicesEnabled' + newvalues(:true, :false) + defaultto :false + + # If authorizationServicesEnabled is set to false it will not be present in + # "get client/" output. Puppet will thus see it as "absent". + # This custom insync? implementation prevents Puppet from trying to set + # the property to false on every run. + def insync?(is) + if is == :true && resource[:authorization_services_enabled] == :true + true + elsif is == :absent && resource[:authorization_services_enabled] == :false + true + else + false + end + end + end + newproperty(:public_client, boolean: true) do desc 'enabled' newvalues(:true, :false) @@ -183,6 +203,12 @@ requires end + validate do + if self[:authorization_services_enabled] == :true && self[:service_accounts_enabled] == :false + raise "Keycloak_client[#{self[:name]}] must have service_accounts_enabled => true if authorization_services_enabled => true" + end + end + def self.title_patterns [ [ diff --git a/lib/puppet/type/keycloak_realm.rb b/lib/puppet/type/keycloak_realm.rb --- a/lib/puppet/type/keycloak_realm.rb +++ b/lib/puppet/type/keycloak_realm.rb @@ -186,4 +186,68 @@ newvalues(:true, :false) defaultto :false end + + newproperty(:smtp_server_user) do + desc 'smtpServer user' + end + + newproperty(:smtp_server_password) do + desc 'smtpServer password' + + def insync?(is) + if is =~ %r{^[\*]+$} + Puppet.warning("Property 'smtp_server_password' is set and Puppet has no way to check current value") + true + else + false + end + end + + def should_to_s(_newvalue) + '[new smtp_server_password redacted]' + end + end + + newproperty(:smtp_server_host) do + desc 'smtpServer host' + end + + newproperty(:smtp_server_port, parent: PuppetX::Keycloak::IntegerProperty) do + desc 'smtpServer port' + end + + newproperty(:smtp_server_auth, boolean: true) do + desc 'smtpServer auth' + newvalues(:true, :false) + end + + newproperty(:smtp_server_starttls, boolean: true) do + desc 'smtpServer starttls' + newvalues(:true, :false) + end + + newproperty(:smtp_server_ssl, boolean: true) do + desc 'smtpServer ssl' + newvalues(:true, :false) + end + + newproperty(:smtp_server_from) do + desc 'smtpServer from' + end + + newproperty(:smtp_server_envelope_from) do + desc 'smtpServer envelope_from' + end + + newproperty(:smtp_server_from_display_name) do + desc 'smtpServer fromDisplayName' + end + + newproperty(:smtp_server_reply_to) do + desc 'smtpServer replyto' + end + + newproperty(:smtp_server_reply_to_display_name) do + desc 'smtpServer replyToDisplayName' + end end diff --git a/lib/puppet/type/keycloak_required_action.rb b/lib/puppet/type/keycloak_required_action.rb new file mode 100644 --- /dev/null +++ b/lib/puppet/type/keycloak_required_action.rb @@ -0,0 +1,137 @@ +require_relative '../../puppet_x/keycloak/type' +require_relative '../../puppet_x/keycloak/integer_property' + +Puppet::Type.newtype(:keycloak_required_action) do + desc <<-DESC +Manage Keycloak required actions +@example Enable Webauthn Register and make it default + keycloak_required_action { 'webauthn-register on master': + ensure => present, + provider_id => 'webauthn-register', + display_name => 'Webauthn Register', + default => true, + enabled => true, + priority => 1, + config => { + 'something' => 'true', # keep in mind that keycloak only supports strings for both keys and values + 'smth else' => '1', + }, + alias => 'webauthn', + } + + @example Minimal example to enable email verification without making it default + keycloak_required_action { 'VERIFY_EMAIL on master': + ensure => present, + provider_id => 'webauthn-register', + } + DESC + + extend PuppetX::Keycloak::Type + + ensurable + + newparam(:name, namevar: true) do + desc 'The required action name' + end + + newparam(:realm, namevar: true) do + desc 'realm' + end + + newparam(:provider_id, namevar: true) do + desc 'providerId of the required action' + munge { |v| v.to_s } + end + + newproperty(:display_name) do + desc 'Displayed name. Default to `provider_id`' + munge { |v| v.to_s } + end + + newproperty(:enabled, boolean: true) do + desc 'If the required action is enabled. Default to true.' + defaultto true + newvalues(:true, :false) + munge { |v| v.to_s == 'true' } + end + + newproperty(:alias) do + desc 'Alias. Default to `provider_id`.' + defaultto do + @resource[:provider_id] + end + end + + newproperty(:default, boolean: true) do + desc 'If the required action is a default one. Default to false' + defaultto false + newvalues(:true, :false) + munge { |v| v.to_s == 'true' } + end + + newproperty(:priority, parent: PuppetX::Keycloak::IntegerProperty) do + desc 'Required action priority' + end + + newproperty(:config) do + desc 'Required action config' + validate do |value| + raise Puppet::Error, 'config must be a Hash' unless value.is_a?(Hash) + end + def insync?(is) + is == @should[0] # for whatever reason puppet makes @should an array, so we actually need to compare with first element + end + + def change_to_s(currentvalue, _newvalue) + if currentvalue == :absent + 'created config' + else + 'changed config' + end + end + + def is_to_s(_currentvalue) # rubocop:disable Style/PredicateName + '[old config redacted]' + end + + def should_to_s(_newvalue) + '[new config redacted]' + end + end + + def self.title_patterns + [ + [ + %r{^((\S+) on (\S+))$}, + [ + [:name], + [:alias], + [:realm], + ], + ], + [ + %r{(.*)}, + [ + [:name], + ], + ], + ] + end + + validate do + required_properties = [ + :alias, + :realm, + ] + required_properties.each do |property| + if self[property].nil? + raise Puppet::Error, "Keycloak_required_action[#{self[:name]}] must have a #{property} defined" + end + end + if self[:ensure] == :present + if self[:provider_id].nil? + raise Puppet::Error, "Keycloak_required_action[#{self[:name]}] provider_id is required" + end + end + end +end diff --git a/manifests/config.pp b/manifests/config.pp --- a/manifests/config.pp +++ b/manifests/config.pp @@ -28,6 +28,7 @@ command => "${_add_user_keycloak_cmd} ${_add_user_keycloak_args} && touch ${_add_user_keycloak_state}", creates => $_add_user_keycloak_state, notify => Class['keycloak::service'], + user => $keycloak::user, } file { "${keycloak::install_base}/tmp": @@ -53,16 +54,29 @@ notify => Class['keycloak::service'], } - file { "${keycloak::install_base}/config.cli": - ensure => 'file', + concat { "${keycloak::install_base}/config.cli": owner => $keycloak::user, group => $keycloak::group, mode => '0600', - content => template('keycloak/config.cli.erb'), notify => Exec['jboss-cli.sh --file=config.cli'], show_diff => false, } + concat::fragment { 'config.cli-keycloak': + target => "${keycloak::install_base}/config.cli", + content => template('keycloak/config.cli.erb'), + order => '00', + } + + if $keycloak::custom_config_content or $keycloak::custom_config_source { + concat::fragment { 'config.cli-custom': + target => "${keycloak::install_base}/config.cli", + content => $keycloak::custom_config_content, + source => $keycloak::custom_config_source, + order => '01', + } + } + exec { 'jboss-cli.sh --file=config.cli': command => "${keycloak::install_base}/bin/jboss-cli.sh --file=config.cli", cwd => $keycloak::install_base, diff --git a/manifests/datasource/postgresql.pp b/manifests/datasource/postgresql.pp --- a/manifests/datasource/postgresql.pp +++ b/manifests/datasource/postgresql.pp @@ -44,7 +44,7 @@ include ::postgresql::server postgresql::server::db { $keycloak::datasource_dbname: user => $keycloak::datasource_username, - password => postgresql_password($keycloak::datasource_username, $keycloak::datasource_password), + password => postgresql::postgresql_password($keycloak::datasource_username, $keycloak::datasource_password), } } } diff --git a/manifests/init.pp b/manifests/init.pp --- a/manifests/init.pp +++ b/manifests/init.pp @@ -56,6 +56,9 @@ # @param group_gid # Keycloak user group GID. # Default is `undef`. +# @param system_user +# If keycloak user should be a system user with lower uid and gid. +# Default is `true` # @param admin_user # Keycloak administrative username. # Default is `admin`. @@ -64,7 +67,6 @@ # Default is `changeme`. # @param manage_datasource # Boolean that determines if configured datasource will be managed. -# Only applies when `datasource_driver` is `mysql`. # Default is `true`. # @param datasource_driver # Datasource driver to use for Keycloak. @@ -167,6 +169,18 @@ # Hash taht is used to define keycloak_flow resources. # @param flow_executions_merge # Boolean that sets if `flows` should be merged from Hiera. +# @param required_actions +# Hash that is used to define keycloak_required_action resources. +# @param required_actions_merge +# Boolean that sets if `required_actions` should be merged from Hiera. +# @param ldap_mappers +# Hash that is used to define keycloak_ldap_mapper resources. +# @param ldap_mappers_merge +# Boolean that sets if `ldap_mappers` should be merged from Hiera. +# @param ldap_user_providers +# Hash that is used to define keycloak_ldap_user_provider resources. +# @param ldap_user_providers_merge +# Boolean that sets if `ldap_user_providers` should be merged from Hiera. # @param with_sssd_support # Boolean that determines if SSSD user provider support should be available # @param libunix_dbus_java_source @@ -189,6 +203,16 @@ # Path to the file with environment variables for the systemd service # @param operating_mode # Keycloak operating mode deployment +# @param enable_jdbc_ping +# Use JDBC_PING to discover the nodes and manage the replication of data +# More info: http://jgroups.org/manual/#_jdbc_ping +# Only applies when `operating_mode` is `clustered` +# JDBC_PING uses port 7600 to ensure cluster members are discoverable by each other +# This module does not manage firewall changes +# @param jboss_bind_public_address +# JBoss bind public IP address +# @param jboss_bind_private_address +# JBoss bind private IP address # @param user_cache # Boolean that determines if userCache is enabled # @param tech_preview_features @@ -199,6 +223,10 @@ # Set if zipped deployments will be auto deployed # @param spi_deployments # Hash used to define keycloak::spi_deployment resources +# @param custom_config_content +# Custom configuration content to be added to config.cli +# @param custom_config_source +# Custom configuration source file to be added to config.cli # class keycloak ( Boolean $manage_install = true, @@ -219,6 +247,7 @@ String $user = 'keycloak', Stdlib::Absolutepath $user_shell = '/sbin/nologin', String $group = 'keycloak', + Boolean $system_user = true, Optional[Integer] $user_uid = undef, Optional[Integer] $group_gid = undef, String $admin_user = 'admin', @@ -261,6 +290,12 @@ Hash $flows = {}, Boolean $flows_merge = false, Hash $flow_executions = {}, + Hash $required_actions = {}, + Boolean $required_actions_merge = false, + Hash $ldap_mappers = {}, + Boolean $ldap_mappers_merge = false, + Hash $ldap_user_providers = {}, + Boolean $ldap_user_providers_merge = false, Boolean $flow_executions_merge = false, Boolean $with_sssd_support = false, Variant[Stdlib::HTTPUrl, Stdlib::HTTPSUrl] @@ -274,11 +309,16 @@ Boolean $restart_sssd = true, Optional[Stdlib::Absolutepath] $service_environment_file = undef, Enum['standalone', 'clustered'] $operating_mode = 'standalone', + Boolean $enable_jdbc_ping = false, + Stdlib::IP::Address $jboss_bind_public_address = $facts['networking']['ip'], + Stdlib::IP::Address $jboss_bind_private_address = $facts['networking']['ip'], Boolean $user_cache = true, Array $tech_preview_features = [], Boolean $auto_deploy_exploded = false, Boolean $auto_deploy_zipped = true, Hash $spi_deployments = {}, + Optional[String] $custom_config_content = undef, + Optional[Variant[String, Array]] $custom_config_source = undef, ) { if ! ($facts['os']['family'] in ['RedHat','Debian']) { diff --git a/manifests/install.pp b/manifests/install.pp --- a/manifests/install.pp +++ b/manifests/install.pp @@ -12,12 +12,14 @@ uid => $keycloak::user_uid, home => '/var/lib/keycloak', managehome => true, + system => $keycloak::system_user, } group { 'keycloak': ensure => 'present', name => $keycloak::group, forcelocal => true, gid => $keycloak::group_gid, + system => $keycloak::system_user, } } diff --git a/manifests/resources.pp b/manifests/resources.pp --- a/manifests/resources.pp +++ b/manifests/resources.pp @@ -48,6 +48,21 @@ } else { $flow_executions = $keycloak::flow_executions } + if $keycloak::required_actions_merge { + $required_actions = lookup('keycloak::required_actions', Hash, 'deep', {}) + } else { + $required_actions = $keycloak::required_actions + } + if $keycloak::ldap_mappers_merge { + $ldap_mappers = lookup('keycloak::ldap_mappers', Hash, 'deep', {}) + } else { + $ldap_mappers = $keycloak::ldap_mappers + } + if $keycloak::ldap_user_providers_merge { + $ldap_user_providers = lookup('keycloak::ldap_user_providers', Hash, 'deep', {}) + } else { + $ldap_user_providers = $keycloak::ldap_user_providers + } $realms.each |$name, $realm| { keycloak_realm { $name: * => $realm } @@ -76,7 +91,16 @@ $flow_executions.each |$name, $data| { keycloak_flow_execution { $name: * => $data } } + $required_actions.each |$name, $data| { + keycloak_required_action { $name: * => $data } + } + $ldap_mappers.each |$name, $data| { + keycloak_ldap_mapper { $name: * => $data } + } + $ldap_user_providers.each |$name, $data| { + keycloak_ldap_user_provider { $name: * => $data } + } $keycloak::spi_deployments.each |$name, $deployment| { keycloak::spi_deployment { $name: * => $deployment } } -} \ No newline at end of file +} diff --git a/metadata.json b/metadata.json --- a/metadata.json +++ b/metadata.json @@ -1,6 +1,6 @@ { "name": "treydock-keycloak", - "version": "6.10.0", + "version": "6.16.0", "author": "treydock", "summary": "Keycloak Puppet module", "license": "Apache-2.0", @@ -18,7 +18,7 @@ }, { "name": "puppetlabs/postgresql", - "version_requirement": ">= 6.2.0 <7.0.0" + "version_requirement": ">= 6.4.0 <7.0.0" }, { "name": "puppetlabs/java", @@ -81,5 +81,5 @@ ], "pdk-version": "1.17.0", "template-url": "https://github.com/treydock/pdk-templates.git#master", - "template-ref": "heads/master-0-gd71639e" + "template-ref": "heads/master-0-g58023b5" } diff --git a/spec/acceptance/10_required_action_spec.rb b/spec/acceptance/10_required_action_spec.rb new file mode 100644 --- /dev/null +++ b/spec/acceptance/10_required_action_spec.rb @@ -0,0 +1,103 @@ +require 'spec_helper_acceptance' + +describe 'required action types:', if: RSpec.configuration.keycloak_full do + context 'creates required action' do + it 'runs successfully' do + pp = <<-EOS + include mysql::server + class { 'keycloak': + datasource_driver => 'mysql', + } + + -> keycloak_realm { 'test': ensure => 'present' } + + -> keycloak_required_action { 'custom-alias on test': + ensure => 'present', + provider_id => 'webauthn-register', + default => true, + enabled => true, + priority => 200, + } + EOS + + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + it 'has configured a required action' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get authentication/required-actions/custom-alias -r test' do + data = JSON.parse(stdout) + expect(data['alias']).to eq('custom-alias') + expect(data['defaultAction']).to eq(true) + expect(data['enabled']).to eq(true) + expect(data['priority']).to eq(200) + end + end + + it 'has the configured required action in list' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get authentication/required-actions -r test' do + data = JSON.parse(stdout) + webauthn = data.find { |d| d['alias'] == 'custom-alias' } + expect(webauthn['priority']).to eq(200) + end + end + end + + context 'updates required action' do + it 'runs successfully' do + pp = <<-EOS + include mysql::server + class { 'keycloak': + datasource_driver => 'mysql', + } + + -> keycloak_realm { 'test': ensure => 'present' } + + -> keycloak_required_action { 'custom-alias on test': + ensure => 'present', + provider_id => 'webauthn-register', + display_name => 'updated name', + default => true, + enabled => true, + priority => 100, + } + EOS + + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + it 'has updated a required action' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get authentication/required-actions/custom-alias -r test' do + data = JSON.parse(stdout) + expect(data['name']).to eq('updated name') + expect(data['priority']).to eq(100) + end + end + end + + context 'ensure => absent' do + it 'runs successfully' do + pp = <<-EOS + include mysql::server + class { 'keycloak': + datasource_driver => 'mysql', + } + -> keycloak_required_action { 'custom-alias on test': + ensure => 'absent' + } + EOS + + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + it 'has deleted a flow' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get authentication/required-actions -r test' do + data = JSON.parse(stdout) + d = data.select { |o| o['alias'] == 'custom-alias' }[0] + expect(d).to be_nil + end + end + end +end diff --git a/spec/acceptance/1_class_spec.rb b/spec/acceptance/1_class_spec.rb --- a/spec/acceptance/1_class_spec.rb +++ b/spec/acceptance/1_class_spec.rb @@ -93,6 +93,41 @@ end end + context 'default with JDBC_PING, clustered mode and postgresql datasource' do + it 'runs successfully' do + pp = <<-EOS + include postgresql::server + class { 'keycloak': + datasource_driver => 'postgresql', + operating_mode => 'clustered', + enable_jdbc_ping => true, + jboss_bind_private_address => '0.0.0.0', + jboss_bind_public_address => '0.0.0.0', + } + EOS + + apply_manifest(pp, catch_failures: true) + apply_manifest(pp, catch_changes: true) + end + + describe service('keycloak') do + it { is_expected.to be_enabled } + it { is_expected.to be_running } + end + + describe port(8080) do + it { is_expected.to be_listening.on('0.0.0.0').with('tcp') } + end + + describe port(9990) do + it { is_expected.to be_listening.on('127.0.0.1').with('tcp') } + end + + describe port(7600) do + it { is_expected.to be_listening.on('0.0.0.0').with('tcp') } + end + end + context 'changes to defaults' do it 'runs successfully' do pp = <<-EOS diff --git a/spec/acceptance/2_realm_spec.rb b/spec/acceptance/2_realm_spec.rb --- a/spec/acceptance/2_realm_spec.rb +++ b/spec/acceptance/2_realm_spec.rb @@ -9,7 +9,18 @@ datasource_driver => 'mysql', } keycloak_realm { 'test': - ensure => 'present', + ensure => 'present', + smtp_server_host => 'smtp.example.org', + smtp_server_port => 587, + smtp_server_starttls => false, + smtp_server_auth => false, + smtp_server_user => 'john', + smtp_server_password => 'secret', + smtp_server_envelope_from => 'keycloak@id.example.org', + smtp_server_from => 'keycloak@id.example.org', + smtp_server_from_display_name => 'Keycloak', + smtp_server_reply_to => 'webmaster@example.org', + smtp_server_reply_to_display_name => 'Webmaster', } EOS @@ -54,6 +65,22 @@ expect(data['adminEventsDetailsEnabled']).to eq(false) end end + + it 'has correct smtp settings' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get realms/test' do + data = JSON.parse(stdout) + expect(data['smtpServer']['host']).to eq('smtp.example.org') + expect(data['smtpServer']['port']).to eq('587') + expect(data['smtpServer']['starttls']).to eq('false') + expect(data['smtpServer']['auth']).to eq('false') + expect(data['smtpServer']['user']).to eq('john') + expect(data['smtpServer']['envelopeFrom']).to eq('keycloak@id.example.org') + expect(data['smtpServer']['from']).to eq('keycloak@id.example.org') + expect(data['smtpServer']['fromDisplayName']).to eq('Keycloak') + expect(data['smtpServer']['replyTo']).to eq('webmaster@example.org') + expect(data['smtpServer']['replyToDisplayName']).to eq('Webmaster') + end + end end context 'updates realm' do @@ -76,6 +103,18 @@ events_expiration => 2678400, admin_events_enabled => true, admin_events_details_enabled => true, + smtp_server_host => 'smtp.example.org', + smtp_server_port => 587, + smtp_server_starttls => false, + smtp_server_auth => true, + smtp_server_user => 'jane', + smtp_server_password => 'secret', + smtp_server_envelope_from => 'keycloak@id.example.org', + smtp_server_from => 'keycloak@id.example.org', + smtp_server_from_display_name => 'Keycloak', + smtp_server_reply_to => 'webmaster@example.org', + smtp_server_reply_to_display_name => 'Hostmaster', + } EOS @@ -92,6 +131,16 @@ expect(data['ssoSessionIdleTimeout']).to eq(3600) expect(data['ssoSessionMaxLifespan']).to eq(72_000) expect(data['browserSecurityHeaders']['contentSecurityPolicy']).to eq("frame-src https://*.duosecurity.com/ 'self'; frame-src 'self'; frame-ancestors 'self'; object-src 'none';") + expect(data['smtpServer']['host']).to eq('smtp.example.org') + expect(data['smtpServer']['port']).to eq('587') + expect(data['smtpServer']['starttls']).to eq('false') + expect(data['smtpServer']['auth']).to eq('true') + expect(data['smtpServer']['user']).to eq('jane') + expect(data['smtpServer']['envelopeFrom']).to eq('keycloak@id.example.org') + expect(data['smtpServer']['from']).to eq('keycloak@id.example.org') + expect(data['smtpServer']['fromDisplayName']).to eq('Keycloak') + expect(data['smtpServer']['replyTo']).to eq('webmaster@example.org') + expect(data['smtpServer']['replyToDisplayName']).to eq('Hostmaster') end end diff --git a/spec/acceptance/5_client_spec.rb b/spec/acceptance/5_client_spec.rb --- a/spec/acceptance/5_client_spec.rb +++ b/spec/acceptance/5_client_spec.rb @@ -10,12 +10,14 @@ } keycloak_realm { 'test': ensure => 'present' } keycloak_client { 'test.foo.bar': - realm => 'test', - root_url => 'https://test.foo.bar', - redirect_uris => ['https://test.foo.bar/test1'], - default_client_scopes => ['address'], - secret => 'foobar', - login_theme => 'keycloak', + realm => 'test', + root_url => 'https://test.foo.bar', + redirect_uris => ['https://test.foo.bar/test1'], + default_client_scopes => ['address'], + secret => 'foobar', + login_theme => 'keycloak', + authorization_services_enabled => false, + service_accounts_enabled => true, } EOS @@ -32,6 +34,8 @@ expect(data['rootUrl']).to eq('https://test.foo.bar') expect(data['redirectUris']).to eq(['https://test.foo.bar/test1']) expect(data['attributes']['login_theme']).to eq('keycloak') + expect(data['authorizationServicesEnabled']).to eq(nil) + expect(data['serviceAccountsEnabled']).to eq(true) end end @@ -52,11 +56,13 @@ } keycloak_realm { 'test': ensure => 'present' } keycloak_client { 'test.foo.bar': - realm => 'test', - root_url => 'https://test.foo.bar/test', - redirect_uris => ['https://test.foo.bar/test2'], - default_client_scopes => ['profile', 'email'], - secret => 'foobar', + realm => 'test', + root_url => 'https://test.foo.bar/test', + redirect_uris => ['https://test.foo.bar/test2'], + default_client_scopes => ['profile', 'email'], + secret => 'foobar', + authorization_services_enabled => true, + service_accounts_enabled => true, } EOS @@ -73,6 +79,8 @@ expect(data['rootUrl']).to eq('https://test.foo.bar/test') expect(data['redirectUris']).to eq(['https://test.foo.bar/test2']) expect(data['attributes']['login_theme']).to be_nil + expect(data['authorizationServicesEnabled']).to eq(true) + expect(data['serviceAccountsEnabled']).to eq(true) end end diff --git a/spec/acceptance/nodesets/centos-8.yml b/spec/acceptance/nodesets/centos-8.yml --- a/spec/acceptance/nodesets/centos-8.yml +++ b/spec/acceptance/nodesets/centos-8.yml @@ -4,7 +4,7 @@ - agent platform: el-8-x86_64 hypervisor: docker - image: centos:8 + image: centos:8.1.1911 docker_preserve_image: true docker_cmd: - '/usr/sbin/init' diff --git a/spec/classes/init_spec.rb b/spec/classes/init_spec.rb --- a/spec/classes/init_spec.rb +++ b/spec/classes/init_spec.rb @@ -31,7 +31,8 @@ shell: shell, gid: 'keycloak', home: '/var/lib/keycloak', - managehome: 'true') + managehome: 'true', + system: 'true') end end @@ -119,17 +120,24 @@ end it do - is_expected.to contain_file("/opt/keycloak-#{version}/config.cli").only_with( - ensure: 'file', + is_expected.to contain_concat("/opt/keycloak-#{version}/config.cli").with( + ensure: 'present', owner: 'keycloak', group: 'keycloak', mode: '0600', - content: %r{.*}, notify: 'Exec[jboss-cli.sh --file=config.cli]', show_diff: 'false', ) end + it do + is_expected.to contain_concat__fragment('config.cli-keycloak').with( + target: "/opt/keycloak-#{version}/config.cli", + content: %r{.*}, + order: '00', + ) + end + it do is_expected.to contain_file_line('standalone.conf-JAVA_OPTS').with( ensure: 'absent', diff --git a/spec/default_facts.yml b/spec/default_facts.yml --- a/spec/default_facts.yml +++ b/spec/default_facts.yml @@ -3,5 +3,6 @@ # Facts specified here will override the values provided by rspec-puppet-facts. --- ipaddress: "172.16.254.254" +ipaddress6: "FE80:0000:0000:0000:AAAA:AAAA:AAAA" is_pe: false macaddress: "AA:AA:AA:AA:AA:AA" diff --git a/spec/fixtures/unit/puppet/provider/keycloak_required_action/kcadm/get-master.out b/spec/fixtures/unit/puppet/provider/keycloak_required_action/kcadm/get-master.out new file mode 100644 --- /dev/null +++ b/spec/fixtures/unit/puppet/provider/keycloak_required_action/kcadm/get-master.out @@ -0,0 +1,49 @@ +[ { + "alias" : "CONFIGURE_TOTP", + "name" : "Configure OTP", + "providerId" : "CONFIGURE_TOTP", + "enabled" : true, + "defaultAction" : false, + "priority" : 10, + "config" : { } + }, { + "alias" : "terms_and_conditions", + "name" : "Terms and Conditions", + "providerId" : "terms_and_conditions", + "enabled" : false, + "defaultAction" : false, + "priority" : 20, + "config" : { } + }, { + "alias" : "UPDATE_PASSWORD", + "name" : "Update Password", + "providerId" : "UPDATE_PASSWORD", + "enabled" : true, + "defaultAction" : false, + "priority" : 30, + "config" : { } + }, { + "alias" : "UPDATE_PROFILE", + "name" : "Update Profile", + "providerId" : "UPDATE_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 40, + "config" : { } + }, { + "alias" : "VERIFY_EMAIL", + "name" : "Verify Email", + "providerId" : "VERIFY_EMAIL", + "enabled" : true, + "defaultAction" : false, + "priority" : 50, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ] diff --git a/spec/fixtures/unit/puppet/provider/keycloak_required_action/kcadm/get-test.out b/spec/fixtures/unit/puppet/provider/keycloak_required_action/kcadm/get-test.out new file mode 100644 --- /dev/null +++ b/spec/fixtures/unit/puppet/provider/keycloak_required_action/kcadm/get-test.out @@ -0,0 +1,68 @@ +[ + { + "alias": "webauthn", + "name": "Webauthn test", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": true, + "priority": 1, + "config": { + "smth else": "1", + "something": "true" + } + }, + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } +] diff --git a/spec/fixtures/unit/puppet/provider/keycloak_required_action/kcadm/get-unregistered-required-actions-master.out b/spec/fixtures/unit/puppet/provider/keycloak_required_action/kcadm/get-unregistered-required-actions-master.out new file mode 100644 --- /dev/null +++ b/spec/fixtures/unit/puppet/provider/keycloak_required_action/kcadm/get-unregistered-required-actions-master.out @@ -0,0 +1,7 @@ +[ { + "providerId" : "webauthn-register-passwordless", + "name" : "Webauthn Register Passwordless" +}, { + "providerId" : "webauthn-register", + "name" : "Webauthn Register" +} ] diff --git a/spec/fixtures/unit/puppet/provider/keycloak_required_action/kcadm/get-unregistered-required-actions-test.out b/spec/fixtures/unit/puppet/provider/keycloak_required_action/kcadm/get-unregistered-required-actions-test.out new file mode 100644 --- /dev/null +++ b/spec/fixtures/unit/puppet/provider/keycloak_required_action/kcadm/get-unregistered-required-actions-test.out @@ -0,0 +1,4 @@ +[ { + "providerId" : "webauthn-register-passwordless", + "name" : "Webauthn Register Passwordless" +} ] diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.configure do |c| c.mock_with :rspec end @@ -40,6 +42,7 @@ # set to strictest setting for testing # by default Puppet runs at warning level Puppet.settings[:strict] = :warning + Puppet.settings[:strict_variables] = true end c.filter_run_excluding(bolt: true) unless ENV['GEM_BOLT'] c.after(:suite) do diff --git a/spec/unit/puppet/provider/keycloak_required_action/kcadm_spec.rb b/spec/unit/puppet/provider/keycloak_required_action/kcadm_spec.rb new file mode 100644 --- /dev/null +++ b/spec/unit/puppet/provider/keycloak_required_action/kcadm_spec.rb @@ -0,0 +1,129 @@ +require 'spec_helper' + +describe Puppet::Type.type(:keycloak_required_action).provider(:kcadm) do + let(:type) do + Puppet::Type.type(:keycloak_required_action) + end + let(:resource) do + type.new(name: 'foo', + realm: 'test', + alias: 'somealias', + provider_id: 'webauthn-register') + end + + describe 'self.instances' do + it 'creates instances' do + allow(described_class).to receive(:realms).and_return(['master', 'test']) + allow(described_class).to receive(:kcadm).with('get', 'authentication/required-actions', 'master').and_return(my_fixture_read('get-master.out')) + allow(described_class).to receive(:kcadm).with('get', 'authentication/unregistered-required-actions', 'master').and_return(my_fixture_read('get-unregistered-required-actions-master.out')) + allow(described_class).to receive(:kcadm).with('get', 'authentication/required-actions', 'test').and_return(my_fixture_read('get-test.out')) + allow(described_class).to receive(:kcadm).with('get', 'authentication/unregistered-required-actions', 'test').and_return(my_fixture_read('get-unregistered-required-actions-test.out')) + + expect(described_class.instances.length).to eq(16) + end + + it 'returns the resource for a required action' do + allow(described_class).to receive(:realms).and_return(['test']) + allow(described_class).to receive(:kcadm).with('get', 'authentication/required-actions', 'test').and_return(my_fixture_read('get-test.out')) + allow(described_class).to receive(:kcadm).with('get', 'authentication/unregistered-required-actions', 'test').and_return('[]') + + property_hash = described_class.instances[0].instance_variable_get('@property_hash') + + expect(property_hash[:name]).to eq('webauthn-register on test') + end + end + + describe 'create' do + it 'registers a required action' do + temp = Tempfile.new('keycloak_required_action_register') + allow(Tempfile).to receive(:new).with('keycloak_required_action_register').and_return(temp) + expect(resource.provider).to receive(:kcadm).with('create', 'authentication/register-required-action', 'test', temp.path) + + resource.provider.create + property_hash = resource.provider.instance_variable_get('@property_hash') + + expect(property_hash[:ensure]).to eq(:present) + end + end + + describe 'destroy' do + it 'deregisters a required action' do + # It suppoed to use whatever came from api and was matched by provider id + # But not what developer provided + resource.provider.instance_variable_set(:@property_hash, alias: 'otheralias') + + expect(resource.provider).to receive(:kcadm).with('delete', 'authentication/required-actions/otheralias', 'test') + + resource.provider.destroy + + property_hash = resource.provider.instance_variable_get('@property_hash') + expect(property_hash).to eq({}) + end + end + + describe 'flush' do + it 'does not do anything without pending changes' do + resource.provider.instance_variable_set(:@property_hash, resource.to_hash) + + expect(resource.provider).not_to receive(:kcadm) + + resource.provider.flush + end + it 'configures a required action' do + resource.provider.instance_variable_set(:@property_hash, resource.to_hash) + temp = Tempfile.new('keycloak_required_action_configure') + allow(Tempfile).to receive(:new).with('keycloak_required_action_configure').and_return(temp) + + expect(resource.provider).to receive(:kcadm).with('update', 'authentication/required-actions/somealias', 'test', temp.path) + + resource.provider.display_name = 'something' + resource.provider.flush + end + + # If developer does not specify the display name, the api would use the name + # that is initially returned from unregistered-required-actions + it 'uses display_name from current state if none specified explicitly' do + resource.provider.instance_variable_set(:@property_hash, display_name: 'display name', alias: 'somealias') + temp = Tempfile.new('keycloak_required_action_configure') + allow(Tempfile).to receive(:new).with('keycloak_required_action_configure').and_return(temp) + + expect(resource.provider).to receive(:kcadm).with('update', 'authentication/required-actions/somealias', 'test', temp.path) + + resource.provider.priority = 1000 + resource.provider.flush + + data = IO.read(temp.path) + json = JSON.parse(data) + expect(json['name']).to eq('display name') + end + + it 'uses provided display_name' do + resource[:display_name] = 'something' + resource.provider.instance_variable_set(:@property_hash, resource.to_hash) + temp = Tempfile.new('keycloak_required_action_configure') + allow(Tempfile).to receive(:new).with('keycloak_required_action_configure').and_return(temp) + + expect(resource.provider).to receive(:kcadm).with('update', 'authentication/required-actions/somealias', 'test', temp.path) + + resource.provider.priority = 200 + resource.provider.flush + + data = IO.read(temp.path) + json = JSON.parse(data) + expect(json['name']).to eq('something') + end + + it 'always uses alias from the current state to make edits' do + resource[:display_name] = 'newalias' + resource.provider.instance_variable_set(:@property_hash, alias: 'current') + + temp = Tempfile.new('keycloak_required_action_configure') + allow(Tempfile).to receive(:new).with('keycloak_required_action_configure').and_return(temp) + + expect(resource.provider).to receive(:kcadm).with('update', 'authentication/required-actions/current', 'test', temp.path) + + resource.provider.priority = 200 + resource.provider.flush + end + end +end diff --git a/spec/unit/puppet/type/keycloak_realm_spec.rb b/spec/unit/puppet/type/keycloak_realm_spec.rb --- a/spec/unit/puppet/type/keycloak_realm_spec.rb +++ b/spec/unit/puppet/type/keycloak_realm_spec.rb @@ -68,6 +68,14 @@ :client_authentication_flow, :docker_authentication_flow, :content_security_policy, + :smtp_server_user, + :smtp_server_password, + :smtp_server_host, + :smtp_server_envelope_from, + :smtp_server_from, + :smtp_server_from_display_name, + :smtp_server_reply_to, + :smtp_server_reply_to_display_name, ].each do |p| it "should accept a #{p}" do config[p] = 'foo' @@ -89,6 +97,7 @@ :access_code_lifespan_user_action, :access_token_lifespan, :access_token_lifespan_for_implicit_flow, + :smtp_server_port, ].each do |p| it "should accept a #{p}" do config[p] = 100 @@ -110,6 +119,9 @@ :events_enabled, :admin_events_enabled, :admin_events_details_enabled, + :smtp_server_auth, + :smtp_server_starttls, + :smtp_server_ssl, ].each do |p| it "should accept true for #{p}" do config[p] = true diff --git a/spec/unit/puppet/type/keycloak_required_action_spec.rb b/spec/unit/puppet/type/keycloak_required_action_spec.rb new file mode 100644 --- /dev/null +++ b/spec/unit/puppet/type/keycloak_required_action_spec.rb @@ -0,0 +1,163 @@ +require 'spec_helper' + +describe Puppet::Type.type(:keycloak_required_action) do + let(:default_config) do + { + name: 'foo', + realm: 'test', + alias: 'something', + provider_id: 'some-provider', + } + end + let(:config) do + default_config + end + let(:resource) do + described_class.new(config) + end + + it 'adds to catalog without raising an error' do + catalog = Puppet::Resource::Catalog.new + expect { + catalog.add_resource resource + }.not_to raise_error + end + + it 'has alias default to provider_id' do + config.delete(:alias) + expect(resource[:alias]).to eq('some-provider') + end + + it 'handles componsite name' do + component = described_class.new(name: 'foo on test', provider_id: 'provider') + expect(component[:name]).to eq('foo on test') + expect(component[:alias]).to eq('foo') + expect(component[:realm]).to eq('test') + end + + defaults = { + enabled: true, + default: false, + } + + describe 'basic properties' do + # Test basic properties + [ + :realm, + :name, + :display_name, + :provider_id, + :alias, + ].each do |p| + it "should accept a #{p}" do + config[p] = 'foo' + expect(resource[p]).to eq('foo') + end + next unless defaults[p] + it "should have default for #{p}" do + expect(resource[p]).to eq(defaults[p]) + end + end + end + + describe 'boolean properties' do + # Test boolean properties + [ + :enabled, + :default, + ].each do |p| + it "should accept true for #{p}" do + config[p] = true + expect(resource[p]).to eq(true) + end + it "should accept true for #{p} string" do + config[p] = 'true' + expect(resource[p]).to eq(true) + end + it "should accept false for #{p}" do + config[p] = false + expect(resource[p]).to eq(false) + end + it "should accept false for #{p} string" do + config[p] = 'false' + expect(resource[p]).to eq(false) + end + it "should not accept strings for #{p}" do + config[p] = 'foo' + expect { + resource + }.to raise_error(%r{foo}) + end + next unless defaults[p] + it "should have default for #{p}" do + expect(resource[p]).to eq(defaults[p]) + end + end + end + + describe 'hash properties' do + # Hash properties + [ + :config, + ].each do |p| + it "should accept hash for #{p}" do + config[p] = { foo: 'bar' } + expect(resource[p]).to eq(foo: 'bar') + end + it 'requires hash' do + config[p] = 'foo' + expect { resource }.to raise_error(%r{must be a Hash}) + end + next unless defaults[p] + it "should have default for #{p}" do + expect(resource[p]).to eq(defaults[p]) + end + end + end + + describe 'integer properties' do + # Integer properties + [ + :priority, + ].each do |p| + it "should accept integer for #{p}" do + config[p] = 1 + expect(resource[p]).to eq(1) + end + it "should accept integer string for #{p}" do + config[p] = '1' + expect(resource[p]).to eq(1) + end + it "should not accept non-integer for #{p}" do + config[p] = 'foo' + expect { resource }.to raise_error(%r{Integer}) + end + next unless defaults[p] + it "should have default for #{p}" do + expect(resource[p]).to eq(defaults[p]) + end + end + end + + describe 'validations' do + it 'requires realm' do + config.delete(:realm) + expect { resource }.to raise_error(%r{must have a realm defined}) + end + it 'requires alias' do + config.delete(:provider_id) + config.delete(:alias) + expect { resource }.to raise_error(%r{must have a alias defined}) + end + it 'requires provider_id when present' do + config.delete(:provider_id) + config[:ensure] = 'present' + expect { resource }.to raise_error(%r{provider_id is required}) + end + it 'does not require provider_id for absent' do + config.delete(:provider_id) + config[:ensure] = 'absent' + expect { resource }.not_to raise_error + end + end +end diff --git a/templates/config.cli.erb b/templates/config.cli.erb --- a/templates/config.cli.erb +++ b/templates/config.cli.erb @@ -7,6 +7,9 @@ if (result.proxy-address-forwarding != true) of /subsystem=undertow/server=default-server/http-listener=default:read-resource /subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding,value=true) end-if +if (result.proxy-address-forwarding != true) of /subsystem=undertow/server=default-server/https-listener=https:read-resource +/subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=proxy-address-forwarding,value=true) +end-if if (outcome != success) of /socket-binding-group=standard-sockets/socket-binding=proxy-https:read-resource /socket-binding-group=standard-sockets/socket-binding=proxy-https:add(port=443) end-if @@ -83,3 +86,34 @@ /subsystem=keycloak-server/spi=userCache/provider=default/:remove /subsystem=keycloak-server/spi=userCache/provider=default/:add(enabled=<%= scope['keycloak::user_cache']%>) end-try +<%- if scope['keycloak::operating_mode'] == 'clustered' && scope['keycloak::enable_jdbc_ping'] -%> +if (outcome != success) of /subsystem=jgroups/stack=tcp/protocol=JDBC_PING:read-resource +<%- if scope['keycloak::datasource_driver'] == 'postgresql' -%> +/subsystem=jgroups/stack=tcp/protocol=JDBC_PING: add(add-index=0, data-source="KeycloakDS", properties=[initialize_sql="CREATE TABLE IF NOT EXISTS JGROUPSPING ( own_addr varchar(200) NOT NULL, cluster_name varchar(200) NOT NULL, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, ping_data BYTEA, constraint PK_JGROUPSPING PRIMARY KEY (own_addr, cluster_name))"]) +<%- end -%> +<%- if scope['keycloak::datasource_driver'] == 'mysql' -%> +/subsystem=jgroups/stack=tcp/protocol=JDBC_PING: add(add-index=0, data-source="KeycloakDS", properties=[initialize_sql="CREATE TABLE IF NOT EXISTS JGROUPSPING (own_addr varchar(200) NOT NULL, cluster_name varchar(200) NOT NULL, updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, ping_data varbinary(5000) DEFAULT NULL, PRIMARY KEY (own_addr, cluster_name)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"]) +<%- end -%> +end-if +if (outcome == success) of /subsystem=jgroups/stack=tcp/protocol=MPING:read-resource +/subsystem=jgroups/stack=tcp/protocol=MPING: remove() +end-if +if (outcome == success) of /subsystem=jgroups/stack=tcp/protocol=pbcast.GMS:read-resource +/subsystem=jgroups/stack=tcp/protocol=pbcast.GMS: remove() +/subsystem=jgroups/stack=tcp/protocol=pbcast.GMS: add(properties=[join_timeout=30000, print_local_addr=true, print_physical_addrs=true]) +end-if +if (outcome != success) of /subsystem=jgroups/stack=tcp/protocol=JDBC_PING:read-resource +end-if +/subsystem=jgroups/channel=ee:write-attribute(name=stack, value="tcp") +if (outcome == success) of /subsystem=jgroups/stack=udp:read-resource +/subsystem=jgroups/stack=udp: remove() +end-if +if (outcome == success) of /socket-binding-group=standard-sockets/socket-binding=jgroups-udp:read-resource +/socket-binding-group=standard-sockets/socket-binding=jgroups-udp:remove() +end-if +if (outcome == success) of /socket-binding-group=standard-sockets/socket-binding=jgroups-mping:read-resource +/socket-binding-group=standard-sockets/socket-binding=jgroups-mping:remove() +end-if +/interface=private:write-attribute(name=inet-address, value=${jboss.bind.address.private:<%= scope['keycloak::jboss_bind_private_address'] %>}) +/interface=public:write-attribute(name=inet-address, value=${jboss.bind.address:<%= scope['keycloak::jboss_bind_public_address'] %>}) +<%- end -%>