diff --git a/.fixtures-latest.yml b/.fixtures-latest.yml index 8623da4..aa8fb6f 100644 --- a/.fixtures-latest.yml +++ b/.fixtures-latest.yml @@ -1,25 +1,29 @@ fixtures: repositories: stdlib: repo: https://github.com/puppetlabs/puppetlabs-stdlib.git mysql: repo: https://github.com/puppetlabs/puppetlabs-mysql.git postgresql: repo: https://github.com/puppetlabs/puppetlabs-postgresql.git java: repo: https://github.com/puppetlabs/puppetlabs-java.git java_ks: repo: https://github.com/puppetlabs/puppetlabs-java_ks.git archive: repo: https://github.com/voxpupuli/puppet-archive.git systemd: repo: https://github.com/camptocamp/puppet-systemd.git + ref: "8f68b0dcf3bbbafc60c025879a28004fc9815aab" yumrepo_core: repo: https://github.com/puppetlabs/puppetlabs-yumrepo_core.git puppet_version: ">= 6.0.0" + augeas_core: + repo: https://github.com/puppetlabs/puppetlabs-augeas_core.git + puppet_version: ">= 6.0.0" apt: repo: https://github.com/puppetlabs/puppetlabs-apt.git concat: repo: https://github.com/puppetlabs/puppetlabs-concat.git symlinks: keycloak: "#{source_dir}" diff --git a/.fixtures.yml b/.fixtures.yml index d94a4d6..2f948b4 100644 --- a/.fixtures.yml +++ b/.fixtures.yml @@ -1,32 +1,37 @@ fixtures: repositories: stdlib: repo: https://github.com/puppetlabs/puppetlabs-stdlib.git ref: 4.25.0 mysql: repo: https://github.com/puppetlabs/puppetlabs-mysql.git ref: v10.2.0 postgresql: repo: https://github.com/puppetlabs/puppetlabs-postgresql.git ref: v6.4.0 java: repo: https://github.com/puppetlabs/puppetlabs-java.git ref: v5.0.0 java_ks: repo: https://github.com/puppetlabs/puppetlabs-java_ks.git ref: 1.4.1 archive: repo: https://github.com/voxpupuli/puppet-archive.git ref: v0.5.1 systemd: repo: https://github.com/camptocamp/puppet-systemd.git ref: 0.4.0 + augeas_core: + repo: https://github.com/puppetlabs/puppetlabs-augeas_core.git + puppet_version: ">= 6.0.0" + ref: 1.1.1 yumrepo_core: repo: https://github.com/puppetlabs/puppetlabs-yumrepo_core.git puppet_version: ">= 6.0.0" + ref: 1.0.7 apt: repo: https://github.com/puppetlabs/puppetlabs-apt.git concat: repo: https://github.com/puppetlabs/puppetlabs-concat.git symlinks: keycloak: "#{source_dir}" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2de08d7..e94b91c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,117 +1,136 @@ name: CI on: push: branches: - main - master pull_request: jobs: unit: runs-on: ubuntu-latest continue-on-error: ${{ matrix.allow_failure }} strategy: fail-fast: false matrix: include: - ruby: 2.4.9 puppet: 5 fixtures: .fixtures.yml allow_failure: false - ruby: 2.5.7 puppet: 6 fixtures: .fixtures.yml allow_failure: false - ruby: 2.4.9 puppet: 5 fixtures: .fixtures-latest.yml allow_failure: true - ruby: 2.5.7 puppet: 6 fixtures: .fixtures-latest.yml allow_failure: true env: BUNDLE_WITHOUT: system_tests:release PUPPET_GEM_VERSION: "~> ${{ matrix.puppet }}.0" FACTER_GEM_VERSION: "< 4.0" FIXTURES_YML: ${{ matrix.fixtures }} name: Puppet ${{ matrix.puppet }} (Ruby ${{ matrix.ruby }} fixtures=${{ matrix.fixtures }}) steps: - uses: actions/checkout@v2 - name: Setup ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true bundler: '2.1.0' - name: Validate run: bundle exec rake check:symlinks check:git_ignore check:dot_underscore check:test_file rubocop syntax lint metadata_lint - name: Run tests run: bundle exec rake parallel_spec acceptance: runs-on: ubuntu-latest strategy: fail-fast: false matrix: set: - "centos-7" - "centos-8" - "debian-9" - "debian-10" - "ubuntu-1804" puppet: - "puppet5" - "puppet6" keycloak_version: - "8.0.1" - "12.0.1" keycloak_full: - "no" + keycloak_domain_mode_cluster: + - "no" include: - set: "centos-7" puppet: "puppet5" keycloak_version: "8.0.1" keycloak_full: "yes" - set: "centos-7" puppet: "puppet5" keycloak_version: "12.0.1" keycloak_full: "yes" - set: "centos-7" puppet: "puppet6" keycloak_version: "8.0.1" keycloak_full: "yes" - set: "centos-7" puppet: "puppet6" keycloak_version: "12.0.1" keycloak_full: "yes" + - set: "centos-7-domain-mode-cluster" + puppet: puppet5 + keycloak_version: 8.0.1 + keycloak_domain_mode_cluster: 'yes' + - set: "centos-7-domain-mode-cluster" + puppet: puppet5 + keycloak_version: 12.0.1 + keycloak_domain_mode_cluster: 'yes' + - set: "centos-7-domain-mode-cluster" + puppet: puppet6 + keycloak_version: 8.0.1 + keycloak_domain_mode_cluster: 'yes' + - set: "centos-7-domain-mode-cluster" + puppet: puppet6 + keycloak_version: 12.0.1 + keycloak_domain_mode_cluster: 'yes' env: BUNDLE_WITHOUT: development:release BEAKER_debug: true name: ${{ matrix.puppet }} ${{ matrix.set }} (keycloak=${{ matrix.keycloak_version }} full=${{ matrix.keycloak_full }}) steps: - name: Enable IPv6 on docker run: | echo '{"ipv6":true,"fixed-cidr-v6":"2001:db8:1::/64"}' | sudo tee /etc/docker/daemon.json sudo service docker restart # https://github.com/actions/virtual-environments/issues/181#issuecomment-610874237 - name: apparmor run: | set -x sudo apt-get remove mysql-server --purge sudo apt-get install apparmor-profiles sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.mysqld - uses: actions/checkout@v2 - name: Setup ruby uses: ruby/setup-ruby@v1 with: ruby-version: '2.7' bundler-cache: true bundler: '2.1.0' - name: Run tests run: bundle exec rake beaker env: BEAKER_PUPPET_COLLECTION: ${{ matrix.puppet }} BEAKER_set: ${{ matrix.set }} BEAKER_keycloak_version: ${{ matrix.keycloak_version }} BEAKER_keycloak_full: ${{ matrix.keycloak_full }} + BEAKER_keycloak_domain_mode_cluster: ${{ matrix.keycloak_domain_mode_cluster }} diff --git a/.gitignore b/.gitignore index 2767022..f0c1c0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,29 @@ .git/ .*.sw[op] .metadata .yardoc .yardwarns *.iml /.bundle/ /.idea/ /.vagrant/ /coverage/ /bin/ /doc/ /Gemfile.local /Gemfile.lock /junit/ /log/ /pkg/ /spec/fixtures/manifests/ /spec/fixtures/modules/ /tmp/ /vendor/ /convert_report.txt /update_report.txt .DS_Store .project .envrc /inventory.yaml +/vagrant/.vagrant/ +/vagrant/*.log diff --git a/README.md b/README.md index 1016f06..17f27e6 100644 --- a/README.md +++ b/README.md @@ -1,506 +1,544 @@ # puppet-module-keycloak [![Puppet Forge](http://img.shields.io/puppetforge/v/treydock/keycloak.svg)](https://forge.puppetlabs.com/treydock/keycloak) [![CI Status](https://github.com/treydock/puppet-module-keycloak/workflows/CI/badge.svg?branch=master)](https://github.com/treydock/puppet-module-keycloak/actions?query=workflow%3ACI) #### Table of Contents 1. [Overview](#overview) * [Supported Versions of Keycloak](#supported-versions-of-keycloak) 2. [Usage - Configuration options](#usage) * [Keycloak](#keycloak) * [Deploy SPI](#deploy-spi) * [keycloak_realm](#keycloak_realm) * [keycloak_ldap_user_provider](#keycloak_ldap_user_provider) * [keycloak_ldap_mapper](#keycloak_ldap_mapper) * [keycloak_sssd_user_provider](#keycloak_sssd_user_provider) * [keycloak_client](#keycloak_client) * [keycloak::client_scope::oidc](#keycloakclient_scopeoidc) * [keycloak::client_scope::saml](#keycloakclient_scopesaml) * [keycloak_client_scope](#keycloak_client_scope) * [keycloak_protocol_mapper](#keycloak_protocol_mapper) * [keycloak_client_protocol_mapper](#keycloak_client_protocol_mapper) * [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) ## Overview The keycloak module allows easy installation and management of Keycloak. ### Supported Versions of Keycloak Currently this module supports Keycloak version 8.x to 12.x. | Keycloak Version | Keycloak Puppet module versions | | ---------------- | ------------------------------- | | 3.x | 2.x | | 4.x - 6.x | 3.x | | 6.x - 8.x | 4.x - 5.x | | 8.x - 12.x | 6.x | ## Usage ### keycloak Install Keycloak using default `h2` database storage. ```puppet class { 'keycloak': } ``` Install a specific version of Keycloak. ```puppet class { 'keycloak': version => '6.0.1', datasource_driver => 'mysql', } ``` Upgrading Keycloak version works by changing `version` parameter as long as the `datasource_driver` is not the default of `h2`. An upgrade involves installing the new version without touching the old version, updating the symlink which defaults to `/opt/keycloak`, applying all changes to new version and then restarting the `keycloak` service. If the previous `version` was `6.0.1` using the following will upgrade to `7.0.0`: ```puppet class { 'keycloak': version => '7.0.0', datasource_driver => 'mysql', } ``` Install keycloak and use a local MySQL server for database storage ```puppet include mysql::server class { 'keycloak': datasource_driver => 'mysql', datasource_host => 'localhost', datasource_port => 3306, datasource_dbname => 'keycloak', datasource_username => 'keycloak', datasource_password => 'foobar', } ``` The following example can be used to configure keycloak with a local PostgreSQL server. ```puppet include postgresql::server class { 'keycloak': datasource_driver => 'postgresql', datasource_host => 'localhost', datasource_port => 5432, datasource_dbname => 'keycloak', datasource_username => 'keycloak', datasource_password => 'foobar', } ``` Configure keycloak to use a remote Oracle database. The parameter `datasource_jar_source` is always required with Oracle database. The jar is downloaded to the keycloak module dir and renamed to `datasource_jar_filename` or `'ojdbc8.jar'` as default value. With a special database configuration it may be more suitable to give the complete database url `'jdbc:oracle:thin:@[...]'` using the parameter `database_url` instead of `database_host`, `database_port` and `database_dbname`. The default value with Oracle database for `database_host` is `'localhost'` and the default value for `database_port` is here `1521`. ```puppet class { 'keycloak': datasource_driver => 'oracle', datasource_host => 'oracleserver.mydomain.de', datasource_port => 1521, datasource_dbname => 'keycloak', datasource_username => 'keycloak', datasource_password => 'foobar', datasource_jar_source => 'https://oracle.com/path/to/driver.jar', datasource_jar_filename => 'ojdbc8.jar', } ``` Configure a SSL certificate truststore and add a LDAP server's certificate to the truststore. ```puppet class { 'keycloak': truststore => true, truststore_password => 'supersecret', truststore_hostname_verification_policy => 'STRICT', } keycloak::truststore::host { 'ldap1.example.com': certificate => '/etc/openldap/certs/0a00000.0', } ``` Setup Keycloak to proxy through Apache HTTPS. ```puppet class { 'keycloak': proxy_https => true } apache::vhost { 'idp.example.com': servername => 'idp.example.com', port => '443', ssl => true, manage_docroot => false, docroot => '/var/www/html', proxy_preserve_host => true, proxy_pass => [ {'path' => '/', 'url' => 'http://localhost:8080/'} ], request_headers => [ 'set X-Forwarded-Proto "https"', 'set X-Forwarded-Port "443"' ], ssl_cert => '/etc/pki/tls/certs/idp.example.com/crt', ssl_key => '/etc/pki/tls/private/idp.example.com.key', } ``` +Setup a domain master. (This needs a shared database, here '1.2.3.4'). + +```puppet +class { '::keycloak': + operating_mode => 'domain', + role => 'master', + wildfly_user => 'wildfly, + wildfly_user_password => 'changeme, + manage_datasource => false, + datasource_driver => 'postgresql', + datasource_host => '1.2.3.4, + datasource_dbname => 'keycloak, + datasource_username => 'keycloak, + datasource_password => 'changeme, + admin_user => 'admin, + admin_user_password => 'changeme, +} +``` + +Setup a domain slave. (This needs a shared database, here '1.2.3.4'). + +```puppet +class { '::keycloak': + operating_mode => 'domain', + role => 'slave', + wildfly_user => 'wildfly, + wildfly_user_password => 'changeme, + manage_datasource => false, + datasource_driver => 'postgresql', + datasource_host => '1.2.3.4, + datasource_dbname => 'keycloak, + datasource_username => 'keycloak, + datasource_password => 'changeme, + admin_user => 'admin, + admin_user_password => 'changeme, +} +``` +**NOTE:** The wilfdly user and password need to match those in domain master. These are required for authentication in a cluster. Setup a host for theme development so that theme changes don't require a service restart, not recommended for production. ```puppet class { 'keycloak': theme_static_max_age => -1, theme_cache_themes => false, theme_cache_templates => false, } ``` Run Keycloak using standalone clustered mode (multicast): ```puppet class { 'keycloak': operating_mode => 'clustered', } ``` 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: ```puppet keycloak::spi_deployment { 'duo-spi': ensure => 'present', deployed_name => 'keycloak-duo-spi-jar-with-dependencies.jar', source => 'https://example.com/files/keycloak-duo-spi-jar-with-dependencies.jar', } ``` The `source` can be a URL or a file path like `/tmp/foo.jar` or prefixed with `file://` or `puppet://` The following example will deploy a custom SPI then check the Keycloak API for the resource to exist. This is useful to ensure SPI is loaded into Keycloak before attempting to add custom resources. ```puppet keycloak::spi_deployment { 'duo-spi': deployed_name => 'keycloak-duo-spi-jar-with-dependencies.jar', source => 'https://example.com/files/keycloak-duo-spi-jar-with-dependencies.jar', test_url => 'authentication/authenticator-providers', test_key => 'id', test_value => 'duo-mfa-authenticator', test_realm => 'test', before => Keycloak_flow_execution['duo-mfa-authenticator under form-browser-with-duo on test'], } ``` ### keycloak_realm Define a Keycloak realm that uses username and not email for login and to use a local branded theme. ```puppet keycloak_realm { 'test': ensure => 'present', remember_me => true, login_with_email_allowed => false, login_theme => 'my_theme', } ``` **NOTE:** If the flow properties such as `browser_flow` are changed from their defaults then this value will not be set when a realm is first created. The value will also not be updated if the flow does not exist. For new realms you will have to run Puppet twice in order to create the flows then update the realm setting. ### keycloak\_ldap\_user_provider Define a LDAP user provider so that authentication can be performed against LDAP. The example below uses two LDAP servers, disables importing of users and assumes the SSL certificates are trusted and do not require being in the truststore. ```puppet keycloak_ldap_user_provider { 'LDAP on test': ensure => 'present', users_dn => 'ou=People,dc=example,dc=com', connection_url => 'ldaps://ldap1.example.com:636 ldaps://ldap2.example.com:636', import_enabled => false, use_truststore_spi => 'never', } ``` **NOTE** The `Id` for the above resource would be `LDAP-test` where the format is `${resource_name}-${realm}`. If you're using FreeIPA you can use a defined resource that wraps keycloak\_ldap\_user\_provider: ```puppet keycloak::freeipa_user_provider { 'ipa.example.org': ensure => 'present', realm => 'EXAMPLE.ORG', bind_dn => 'uid=ldapproxy,cn=sysaccounts,cn=etc,dc=example,dc=org', bind_credential => 'secret', users_dn => 'cn=users,cn=accounts,dc=example,dc=org', priority => 10, } ``` ### keycloak\_ldap_mapper Use the LDAP attribute 'gecos' as the full name attribute. ```puppet keycloak_ldap_mapper { 'full name for LDAP-test on test: ensure => 'present', resource_name => 'full name', type => 'full-name-ldap-mapper', ldap_attribute => 'gecos', } ``` If you're using FreeIPA you can use a defined resource that adds all the required attribute mappings automatically: ```puppet keycloak::freeipa_ldap_mappers { 'ipa.example.org': realm => 'EXAMPLE.ORG', groups_dn => 'cn=groups,cn=accounts,dc=example,dc=org', roles_dn => 'cn=groups,cn=accounts,dc=example,dc=org' } ``` ### keycloak\_sssd\_user\_provider Define SSSD user provider. **NOTE** This type requires that SSSD be properly configured and Keycloak service restarted after SSSD ifp service is setup. Also requires `keycloak` class be called with `with_sssd_support` set to `true`. ```puppet keycloak_sssd_user_provider { 'SSSD on test': ensure => 'present', } ``` ### keycloak_client Register a client. ```puppet keycloak_client { 'www.example.com': ensure => 'present', realm => 'test', redirect_uris => [ "https://www.example.com/oidc", "https://www.example.com", ], client_template => 'oidc-clients', secret => 'supersecret', } ``` ### keycloak::client_scope::oidc Defined type that can be used to define both `keycloak_client_scope` and `keycloak_protocol_mapper` resources for OpenID Connect. ```puppet keycloak::client_scope::oidc { 'oidc-clients': realm => 'test', } ``` ### keycloak::client_scope::saml Defined type that can be used to define both `keycloak_client_scope` and `keycloak_protocol_mapper` resources for SAML. ```puppet keycloak::client_scope::saml { 'saml-clients': realm => 'test', } ``` ### keycloak\_client_scope Define a Client Scope of `email` for realm `test` in Keycloak: ```puppet keycloak_client_scope { 'email on test': protocol => 'openid-connect', } ``` ### keycloak\_protocol_mapper Associate a Protocol Mapper to a given Client Scope. The name in the following example will add the `email` protocol mapper to client scope `oidc-email` in the realm `test`. ```puppet keycloak_protocol_mapper { "email for oidc-email on test": claim_name => 'email', user_attribute => 'email', } ``` ### keycloak\_client\_protocol\_mapper Add `email` protocol mapper to `test.example.com` client in realm `test` ```puppet keycloak_client_protocol_mapper { "email for test.example.com on test": claim_name => 'email', user_attribute => 'email', } ``` ### keycloak\_identity\_provider Add `cilogon` identity provider to `test` realm ```puppet keycloak_identity_provider { 'cilogon on test': ensure => 'present', display_name => 'CILogon', provider_id => 'oidc', first_broker_login_flow_alias => 'browser', client_id => 'cilogon:/client_id/foobar', client_secret => 'supersecret', user_info_url => 'https://cilogon.org/oauth2/userinfo', token_url => 'https://cilogon.org/oauth2/token', authorization_url => 'https://cilogon.org/authorize', } ``` ### Keycloak Flows The following is an example of deploying a custom Flow. The name for the top level flow is `$alias on $realm` The name for an execution is `$provider under $flow on $realm`. The name for the flow under a top level flow is `$alias under $flow_alias on $realm`. ```puppet keycloak_flow { 'browser-with-duo on test': ensure => 'present', } keycloak_flow_execution { 'auth-cookie under browser-with-duo on test': ensure => 'present', configurable => false, display_name => 'Cookie', index => 0, requirement => 'ALTERNATIVE', } keycloak_flow_execution { 'identity-provider-redirector under browser-with-duo on test': ensure => 'present', configurable => true, display_name => 'Identity Provider Redirector', index => 1, requirement => 'ALTERNATIVE', } keycloak_flow { 'form-browser-with-duo under browser-with-duo on test': ensure => 'present', index => 2, requirement => 'ALTERNATIVE', top_level => false, } keycloak_flow_execution { 'auth-username-password-form under form-browser-with-duo on test': ensure => 'present', configurable => false, display_name => 'Username Password Form', index => 0, requirement => 'REQUIRED', } keycloak_flow_execution { 'duo-mfa-authenticator under form-browser-with-duo on test': ensure => 'present', configurable => true, display_name => 'Duo MFA', alias => 'Duo', config => { "duomfa.akey" => "foo-akey", "duomfa.apihost" => "api-foo.duosecurity.com", "duomfa.skey" => "secret", "duomfa.ikey" => "foo-ikey", "duomfa.groups" => "duo" }, requirement => 'REQUIRED', index => 1, } ``` ### keycloak\_api The keycloak_api type can be used to define how this module's types access the Keycloak API if this module is only used for the types/providers and the module's `kcadm-wrapper.sh` is not installed. ```puppet keycloak_api { 'keycloak' install_dir => '/opt/keycloak', server => 'http://localhost:8080/auth', realm => 'master', user => 'admin', password => 'changeme', } ``` 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/) ## Limitations This module has been tested on: * CentOS 7 x86_64 * RedHat 7 x86_64 * Debian 9 x86_64 * Ubuntu 18.04 x86_64 diff --git a/manifests/config.pp b/manifests/config.pp index 6c5dca3..a933212 100644 --- a/manifests/config.pp +++ b/manifests/config.pp @@ -1,115 +1,276 @@ # Private class. class keycloak::config { assert_private() file { '/opt/keycloak': ensure => 'link', target => $keycloak::install_base, } # Template uses: # - $keycloak::install_base # - $keycloak::admin_user # - $keycloak::admin_user_password file { 'kcadm-wrapper.sh': ensure => 'file', path => "${keycloak::install_base}/bin/kcadm-wrapper.sh", owner => $keycloak::user, group => $keycloak::group, mode => '0750', content => template('keycloak/kcadm-wrapper.sh.erb'), show_diff => false, } - $_add_user_keycloak_cmd = "${keycloak::install_base}/bin/add-user-keycloak.sh" - $_add_user_keycloak_args = "--user ${keycloak::admin_user} --password ${keycloak::admin_user_password} --realm master" - $_add_user_keycloak_state = "${keycloak::install_base}/.create-keycloak-admin-${keycloak::datasource_driver}" - exec { 'create-keycloak-admin': - 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": ensure => 'directory', owner => $keycloak::user, group => $keycloak::group, mode => '0755', } - file { "${keycloak::install_base}/standalone/configuration": - ensure => 'directory', - owner => $keycloak::user, - group => $keycloak::group, - mode => '0750', + $_add_user_keycloak_cmd = "${keycloak::install_base}/bin/add-user-keycloak.sh" + $_add_user_keycloak_state = "${keycloak::install_base}/.create-keycloak-admin-${keycloak::datasource_driver}" + $_config_cli_content = template('keycloak/config.cli.erb') + + if $::keycloak::operating_mode != 'domain' { + $_add_user_keycloak_args = "--user ${keycloak::admin_user} --password ${keycloak::admin_user_password} --realm master" + $_subdir = 'standalone' + $_java_opts_path = "${keycloak::install_base}/bin/standalone.conf" + } else { + $_server_conf_dir = "${keycloak::install_base}/domain/servers/${keycloak::server_name}/configuration" + $_add_user_keycloak_args = "--user ${keycloak::admin_user} --password ${keycloak::admin_user_password} --realm master --sc ${_server_conf_dir}/" # lint:ignore:140chars + $_subdir = 'domain' + $_java_opts_path = "${keycloak::install_base}/bin/domain.conf" + + $_dirs = [ + "${keycloak::install_base}/domain/servers", + "${keycloak::install_base}/domain/servers/${keycloak::server_name}", + "${keycloak::install_base}/domain/servers/${keycloak::server_name}/configuration", + ] + + file { $_dirs: + ensure => 'directory', + owner => $keycloak::user, + group => $keycloak::group, + mode => '0755', + } } - file { "${keycloak::install_base}/standalone/configuration/profile.properties": - ensure => 'file', - owner => $keycloak::user, - group => $keycloak::group, - content => template('keycloak/profile.properties.erb'), - mode => '0644', + exec { 'create-keycloak-admin': + 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, } concat { "${keycloak::install_base}/config.cli": owner => $keycloak::user, group => $keycloak::group, mode => '0600', 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'), + content => $_config_cli_content, 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, user => $keycloak::user, group => $keycloak::group, refreshonly => true, logoutput => true, notify => Class['keycloak::service'], } create_resources('keycloak::truststore::host', $keycloak::truststore_hosts) if $keycloak::java_opts { $java_opts_ensure = 'present' } else { $java_opts_ensure = 'absent' } if $keycloak::java_opts =~ Array { $java_opts = join($keycloak::java_opts, ' ') } else { $java_opts = $keycloak::java_opts } if $keycloak::java_opts_append { $_java_opts = "\$JAVA_OPTS ${java_opts}" } else { $_java_opts = $java_opts } - file_line { 'standalone.conf-JAVA_OPTS': + file_line { 'keycloak-JAVA_OPTS': ensure => $java_opts_ensure, - path => "${keycloak::install_base}/bin/standalone.conf", + path => $_java_opts_path, line => "JAVA_OPTS=\"${_java_opts}\"", match => '^JAVA_OPTS=', notify => Class['keycloak::service'], } + + file { "${keycloak::install_base}/${_subdir}/configuration": + ensure => 'directory', + owner => $keycloak::user, + group => $keycloak::group, + mode => '0750', + } + + file { "${keycloak::install_base}/${_subdir}/configuration/profile.properties": + ensure => 'file', + owner => $keycloak::user, + group => $keycloak::group, + content => template('keycloak/profile.properties.erb'), + mode => '0644', + notify => Class['keycloak::service'], + } + + if $::keycloak::operating_mode == 'domain' { + $_add_user_wildfly_cmd = "${keycloak::install_base}/bin/add-user.sh" + $_add_user_wildfly_args = "--user ${keycloak::wildfly_user} --password ${keycloak::wildfly_user_password} -e -s" + $_add_user_wildfly_state = "${::keycloak::install_base}/.create-wildfly-user" + + exec { 'create-wildfly-user': + command => "${_add_user_wildfly_cmd} ${_add_user_wildfly_args} && touch ${_add_user_wildfly_state}", + creates => $_add_user_wildfly_state, + notify => Class['keycloak::service'], + } + + if $keycloak::role == 'master' { + + # Remove load balancer group + # Rename the server + # Set port offset to zero to run server on port 8080 + augeas { 'ensure-servername': + incl => "${keycloak::install_base}/domain/configuration/host-master.xml", + context => "/files${keycloak::install_base}/domain/configuration/host-master.xml/host/servers", + load_path => '/opt/puppetlabs/puppet/share/augeas/lenses/dist', + lens => 'Xml.lns', + changes => [ + 'rm server[1]', + 'rm server', + "set server/#attribute/name ${keycloak::server_name}", + 'set server/#attribute/group auth-server-group', + 'set server/#attribute/auto-start true', + 'set server/socket-bindings/#attribute/port-offset 0', + ], + notify => Class['keycloak::service'], + } + + # Set up interface names and defaults in host-master.xml + augeas { 'ensure-interface-names-defaults-master': + incl => "${keycloak::install_base}/domain/configuration/host-master.xml", + context => "/files${keycloak::install_base}/domain/configuration/host-master.xml/host/interfaces", + load_path => '/opt/puppetlabs/puppet/share/augeas/lenses/dist', + lens => 'Xml.lns', + changes => [ + # lint:ignore:single_quote_string_with_variables + 'set interface[1]/#attribute/name management', + 'set interface[1]/inet-address/#attribute/value ${jboss.bind.address.management:127.0.0.1}', + 'set interface[2]/#attribute/name private', + 'set interface[2]/inet-address/#attribute/value ${jboss.bind.address.private:127.0.0.1}', + 'set interface[3]/#attribute/name public', + 'set interface[3]/inet-address/#attribute/value ${jboss.bind.address:127.0.0.1}', + # lint:endignore + ], + notify => Class['keycloak::service'], + } + + # Assing management interfaces to logical interfaces + augeas { 'assign-management-interaces-master': + incl => "${keycloak::install_base}/domain/configuration/host-master.xml", + context => "/files${keycloak::install_base}/domain/configuration/host-master.xml/host/management/management-interfaces", + load_path => '/opt/puppetlabs/puppet/share/augeas/lenses/dist', + lens => 'Xml.lns', + changes => [ + 'set native-interface/socket/#attribute/interface management', + 'set http-interface/socket/#attribute/interface private', + ], + notify => Class['keycloak::service'], + } + } else { + # Rename the server + # Set port offset to zero, to run server in port 8080 + augeas { 'ensure-servername': + incl => "${keycloak::install_base}/domain/configuration/host-slave.xml", + context => "/files${keycloak::install_base}/domain/configuration/host-slave.xml/host/servers", + load_path => '/opt/puppetlabs/puppet/share/augeas/lenses/dist', + lens => 'Xml.lns', + changes => [ + "set server/#attribute/name ${keycloak::server_name}", + 'set server/socket-bindings/#attribute/port-offset 0' + ], + notify => Class['keycloak::service'], + } + + # Set username for authentication to master + augeas { 'ensure-username': + incl => "${keycloak::install_base}/domain/configuration/host-slave.xml", + context => "/files${keycloak::install_base}/domain/configuration/host-slave.xml/host/domain-controller/remote", + load_path => '/opt/puppetlabs/puppet/share/augeas/lenses/dist', + lens => 'Xml.lns', + changes => [ + "set #attribute/username ${keycloak::wildfly_user}" + ], + notify => Class['keycloak::service'], + } + + # Set secret for authentication to master + augeas { 'ensure-secret': + incl => "${keycloak::install_base}/domain/configuration/host-slave.xml", + context => "/files${keycloak::install_base}/domain/configuration/host-slave.xml/host/management/security-realms/security-realm[1]/server-identities/secret", # lint:ignore:140chars + load_path => '/opt/puppetlabs/puppet/share/augeas/lenses/dist', + lens => 'Xml.lns', + changes => [ + "set #attribute/value ${keycloak::wildfly_user_password_base64}" + ], + notify => Class['keycloak::service'], + } + + # Set up interface names and default in host-slave.xml + augeas { 'ensure-interface-names-defaults-slave': + incl => "${keycloak::install_base}/domain/configuration/host-slave.xml", + context => "/files${keycloak::install_base}/domain/configuration/host-slave.xml/host/interfaces", + load_path => '/opt/puppetlabs/puppet/share/augeas/lenses/dist', + lens => 'Xml.lns', + changes => [ + # lint:ignore:single_quote_string_with_variables + 'set interface[1]/#attribute/name management', + 'set interface[1]/inet-address/#attribute/value ${jboss.bind.address.management:127.0.0.1}', + 'set interface[2]/#attribute/name private', + 'set interface[2]/inet-address/#attribute/value ${jboss.bind.address.private:127.0.0.1}', + 'set interface[3]/#attribute/name public', + 'set interface[3]/inet-address/#attribute/value ${jboss.bind.address:127.0.0.1}', + # lint:endignore + ], + notify => Class['keycloak::service'], + } + + # Assing management interfaces to logical interfaces + augeas { 'assign-management-interaces-slave': + incl => "${keycloak::install_base}/domain/configuration/host-slave.xml", + context => "/files${keycloak::install_base}/domain/configuration/host-slave.xml/host/management/management-interfaces", + load_path => '/opt/puppetlabs/puppet/share/augeas/lenses/dist', + lens => 'Xml.lns', + changes => [ + 'set native-interface/socket/#attribute/interface management', + 'set http-interface/socket/#attribute/interface private', + ], + notify => Class['keycloak::service'], + } + } + } } diff --git a/manifests/init.pp b/manifests/init.pp index 8dbef44..2629c24 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -1,425 +1,469 @@ # @summary Manage Keycloak # # @example # include ::keycloak # # @param manage_install # Install Keycloak from upstream Keycloak tarball. # Set to false to manage installation of Keycloak outside # this module and set $install_dir to match. # Defaults to true. # @param version # Version of Keycloak to install and manage. # @param package_url # URL of the Keycloak download. # Default is based on version. # @param install_dir # The directory of where to install Keycloak. # Default is `/opt/keycloak-${version}`. # @param service_name # Keycloak service name. # Default is `keycloak`. # @param service_ensure # Keycloak service ensure property. # Default is `running`. # @param service_enable # Keycloak service enable property. # Default is `true`. # @param service_hasstatus # Keycloak service hasstatus parameter. # Default is `true`. # @param service_hasrestart # Keycloak service hasrestart parameter. # Default is `true`. # @param service_bind_address # Bind address for Keycloak service. # Default is '0.0.0.0'. +# @param management_bind_address +# Bind address for Keycloak management. +# Default is '0.0.0.0'. # @param java_opts # Sets additional options to Java virtual machine environment variable. # @param java_opts_append # Determine if $JAVA_OPTS should be appended to when setting `java_opts` parameter # @param service_extra_opts # Additional options added to the end of the service command-line. # @param manage_user # Defines if the module should manage the Linux user for Keycloak installation # @param user # Keycloak user name. # Default is `keycloak`. # @param user_shell # Keycloak user shell. # @param group # Keycloak user group name. # Default is `keycloak`. # @param user_uid # Keycloak user UID. # Default is `undef`. # @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`. # @param admin_user_password # Keycloak administrative user password. # Default is `changeme`. +# @param wildfly_user +# Wildfly user. Required for domain mode. +# @param wildfly_user_password +# Wildfly user password. Required for domain mode. # @param manage_datasource # Boolean that determines if configured datasource will be managed. # Default is `true`. # @param datasource_driver # Datasource driver to use for Keycloak. # Valid values are `h2`, `mysql`, 'oracle' and 'postgresql' # Default is `h2`. # @param datasource_host # Datasource host. # Only used when datasource_driver is `mysql`, 'oracle' or 'postgresql' # Default is `localhost` for MySQL. # @param datasource_port # Datasource port. # Only used when datasource_driver is `mysql`, 'oracle' or 'postgresql' # Default is `3306` for MySQL. # @param datasource_url # Datasource url. # Default datasource URLs are defined in init class. # @param datasource_dbname # Datasource database name. # Default is `keycloak`. # @param datasource_username # Datasource user name. # Default is `sa`. # @param datasource_password # Datasource user password. # Default is `sa`. # @param datasource_package # Package to add specified datasource support # @param datasource_jar_source # Source for datasource JDBC driver - could be puppet link or local file on the node. # Default is dependent on value for `datasource_driver`. # This parameter is required if `datasource_driver` is `oracle`. # @param datasource_jar_filename # Specify the filename of the destination datasource jar in the module dir of keycloak. # This parameter is only working at the moment if `datasource_driver` is `oracle`. # @param datasource_module_source # Source for datasource module.xml. Default depends on `datasource_driver`. # @param datasource_xa_class # MySQL Connector/J JDBC driver xa-datasource class name # @param proxy_https # Boolean that sets if HTTPS proxy should be enabled. # Set to `true` if proxying traffic through Apache. # Default is `false`. # @param truststore # Boolean that sets if truststore should be used. # Default is `false`. # @param truststore_hosts # Hash that is used to define `keycloak::turststore::host` resources. # Default is `{}`. # @param truststore_password # Truststore password. # Default is `keycloak`. # @param truststore_hostname_verification_policy # Valid values are `WILDCARD`, `STRICT`, and `ANY`. # Default is `WILDCARD`. # @param http_port # HTTP port used by Keycloak. # Default is `8080`. # @param theme_static_max_age # Max cache age in seconds of static content. # Default is `2592000`. # @param theme_cache_themes # Boolean that sets if themes should be cached. # Default is `true`. # @param theme_cache_templates # Boolean that sets if templates should be cached. # Default is `true`. # @param realms # Hash that is used to define keycloak_realm resources. # Default is `{}`. # @param realms_merge # Boolean that sets if `realms` should be merged from Hiera. # @param oidc_client_scopes # Hash that is used to define keycloak::client_scope::oidc resources. # Default is `{}`. # @param oidc_client_scopes_merge # Boolean that sets if `oidc_client_scopes` should be merged from Hiera. # @param saml_client_scopes # Hash that is used to define keycloak::client_scope::saml resources. # Default is `{}`. # @param saml_client_scopes_merge # Boolean that sets if `saml_client_scopes` should be merged from Hiera. # @param identity_providers # Hash that is used to define keycloak_identity_provider resources. # @param identity_providers_merge # Boolean that sets if `identity_providers` should be merged from Hiera. # @param client_protocol_mappers # Hash that is used to define keycloak_client_protocol_mapper resources. # @param client_scopes # Hash that is used to define keycloak_client_scope resources. # @param client_scopes_merge # Boolean that sets if `client_scopes` should be merged from Hiera. # @param protocol_mappers # Hash that is used to define keycloak_protocol_mapper resources. # @param protocol_mappers_merge # Boolean that sets if `protocol_mappers` should be merged from Hiera. # @param clients # Hash that is used to define keycloak_client resources. # @param clients_merge # Boolean that sets if `clients` should be merged from Hiera. # @param flows # Hash taht is used to define keycloak_flow resources. # @param flows_merge # Boolean that sets if `flows` should be merged from Hiera. # @param flow_executions # 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 # Source URL of libunix-dbus-java # @param install_libunix_dbus_java_build_dependencies # Boolean that determines of libunix-dbus-java build dependencies are managed by this module # @param libunix_dbus_java_build_dependencies # Packages needed to build libunix-dbus-java # @param libunix_dbus_java_libdir # Path to directory to install libunix-dbus-java libraries # @param jna_package_name # Package name for jna # @param manage_sssd_config # Boolean that determines if SSSD ifp config for Keycloak is managed # @param sssd_ifp_user_attributes # user_attributes to define for SSSD ifp service # @param restart_sssd # Boolean that determines if SSSD should be restarted # @param service_environment_file # 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` +# Only applies when `operating_mode` is either `clustered` or `domain` # 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 role +# Role when operating mode is domain. # @param user_cache # Boolean that determines if userCache is enabled # @param tech_preview_features # List of technology Preview features to enable # @param auto_deploy_exploded # Set if exploded deployements will be auto deployed # @param auto_deploy_zipped # 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 -# +# @param master_address +# IP address of the master in domain mode +# @param server_name +# Server name in domain mode. Defaults to hostname. class keycloak ( Boolean $manage_install = true, String $version = '8.0.1', Optional[Variant[Stdlib::HTTPUrl, Stdlib::HTTPSUrl]] $package_url = undef, Optional[Stdlib::Absolutepath] $install_dir = undef, String $service_name = 'keycloak', String $service_ensure = 'running', Boolean $service_enable = true, Boolean $service_hasstatus = true, Boolean $service_hasrestart = true, Stdlib::IP::Address $service_bind_address = '0.0.0.0', + Stdlib::IP::Address $management_bind_address = '0.0.0.0', Optional[Variant[String, Array]] $java_opts = undef, Boolean $java_opts_append = true, Optional[String] $service_extra_opts = undef, Boolean $manage_user = true, 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', String $admin_user_password = 'changeme', + Optional[String] $wildfly_user = undef, + Optional[String] $wildfly_user_password = undef, Boolean $manage_datasource = true, Enum['h2', 'mysql', 'oracle', 'postgresql'] $datasource_driver = 'h2', Optional[String] $datasource_host = undef, Optional[Integer] $datasource_port = undef, Optional[String] $datasource_url = undef, Optional[String] $datasource_xa_class = undef, String $datasource_dbname = 'keycloak', String $datasource_username = 'sa', String $datasource_password = 'sa', Optional[String] $datasource_package = undef, Optional[String] $datasource_jar_source = undef, Optional[String] $datasource_jar_filename = undef, Optional[String] $datasource_module_source = undef, Boolean $proxy_https = false, Boolean $truststore = false, Hash $truststore_hosts = {}, String $truststore_password = 'keycloak', Enum['WILDCARD', 'STRICT', 'ANY'] $truststore_hostname_verification_policy = 'WILDCARD', Integer $http_port = 8080, Integer $theme_static_max_age = 2592000, Boolean $theme_cache_themes = true, Boolean $theme_cache_templates = true, Hash $realms = {}, Boolean $realms_merge = false, Hash $oidc_client_scopes = {}, Boolean $oidc_client_scopes_merge = false, Hash $saml_client_scopes = {}, Boolean $saml_client_scopes_merge = false, Hash $client_protocol_mappers = {}, Hash $client_scopes = {}, Boolean $client_scopes_merge = false, Hash $protocol_mappers = {}, Boolean $protocol_mappers_merge = false, Hash $identity_providers = {}, Boolean $identity_providers_merge = false, Hash $clients = {}, Boolean $clients_merge = false, 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] $libunix_dbus_java_source = 'https://github.com/keycloak/libunix-dbus-java/archive/libunix-dbus-java-0.8.0.tar.gz', Boolean $install_libunix_dbus_java_build_dependencies = true, Array $libunix_dbus_java_build_dependencies = [], Stdlib::Absolutepath $libunix_dbus_java_libdir = '/usr/lib64', String $jna_package_name = 'jna', Boolean $manage_sssd_config = true, Array $sssd_ifp_user_attributes = [], Boolean $restart_sssd = true, Optional[Stdlib::Absolutepath] $service_environment_file = undef, - Enum['standalone', 'clustered'] $operating_mode = 'standalone', + Enum['standalone', 'clustered', 'domain'] $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'], + Optional[Enum['master', 'slave']] $role = undef, 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, + Optional[Stdlib::Host] $master_address = undef, + String $server_name = $facts['hostname'], ) { if ! ($facts['os']['family'] in ['RedHat','Debian']) { fail("Unsupported osfamily: ${facts['os']['family']}, module ${module_name} only support osfamilies Debian and Redhat") } + if $role and ! ($operating_mode == 'domain') { + fail('Role can only be specified in domain operating mode') + } + + if $operating_mode == 'domain' { + unless $role { + fail("Role not specified: in domain mode role needs to be specified. This needs to be either 'master' or 'slave'.") + } + unless $wildfly_user { + fail('Wildfly user not specified: in domain mode Wildfly user needs to be specified.') + } + unless $wildfly_user_password { + fail('Wildfly user password not specified: in domain, mode Wildfly user password needs to be specified.') + } + + if $role == 'slave' and ! $master_address { + fail('Master address not specified: in domain mode, master address needs to be specified for a slave.') + } + + if $datasource_driver == 'h2' { + fail("Invalid datasource driver for domain mode: ${datasource_driver}") + } + + $wildfly_user_password_base64 = strip(base64('encode', $wildfly_user_password)) + } + if versioncmp($version, '12.0.0') >= 0 { $download_url = pick($package_url, "https://github.com/keycloak/keycloak/releases/download/${version}/keycloak-${version}.tar.gz") } else { $download_url = pick($package_url, "https://downloads.jboss.org/keycloak/${version}/keycloak-${version}.tar.gz") } case $datasource_driver { 'h2': { $datasource_connection_url = pick($datasource_url, "jdbc:h2:\${jboss.server.data.dir}/${datasource_dbname};AUTO_SERVER=TRUE") } 'mysql': { $db_host = pick($datasource_host, 'localhost') $db_port = pick($datasource_port, 3306) $datasource_connection_url = pick($datasource_url, "jdbc:mysql://${db_host}:${db_port}/${datasource_dbname}") } 'oracle': { $db_host = pick($datasource_host, 'localhost') $db_port = pick($datasource_port, 1521) $datasource_connection_url = pick($datasource_url, "jdbc:oracle:thin:@${db_host}:${db_port}:${datasource_dbname}") } 'postgresql': { $db_host = pick($datasource_host, 'localhost') $db_port = pick($datasource_port, 5432) $datasource_connection_url = pick($datasource_url, "jdbc:postgresql://${db_host}:${db_port}/${datasource_dbname}") } default: {} } if ($datasource_driver == 'oracle') and ($datasource_jar_source == undef) { fail('Using Oracle RDBMS requires definition datasource_jar_source for Oracle JDBC driver. Refer to module documentation') } case $facts['os']['family'] { 'RedHat': { if versioncmp($facts['os']['release']['major'], '8') >= 0 { $mysql_datasource_class = pick($datasource_xa_class, 'org.mariadb.jdbc.MariaDbDataSource') $mysql_jar_source = '/usr/lib/java/mariadb-java-client.jar' $postgresql_jar_source = '/usr/share/java/postgresql-jdbc/postgresql.jar' } else { $mysql_datasource_class = pick($datasource_xa_class, 'com.mysql.jdbc.jdbc2.optional.MysqlXADataSource') $mysql_jar_source = '/usr/share/java/mysql-connector-java.jar' $postgresql_jar_source = '/usr/share/java/postgresql-jdbc.jar' } } 'Debian': { if $facts['os']['name'] == 'Debian' and versioncmp($facts['os']['release']['major'], '10') >= 0 { $mysql_datasource_class = pick($datasource_xa_class, 'org.mariadb.jdbc.MariaDbDataSource') $mysql_jar_source = '/usr/share/java/mariadb-java-client.jar' } else { $mysql_datasource_class = pick($datasource_xa_class, 'com.mysql.jdbc.jdbc2.optional.MysqlXADataSource') $mysql_jar_source = '/usr/share/java/mysql-connector-java.jar' } $postgresql_jar_source = '/usr/share/java/postgresql.jar' } default: { # do nothing } } $install_base = pick($install_dir, "/opt/keycloak-${keycloak::version}") include ::java contain 'keycloak::install' contain "keycloak::datasource::${datasource_driver}" contain 'keycloak::config' contain 'keycloak::service' Class['::java'] -> Class['keycloak::install'] -> Class["keycloak::datasource::${datasource_driver}"] -> Class['keycloak::config'] -> Class['keycloak::service'] Class["keycloak::datasource::${datasource_driver}"]~>Class['keycloak::service'] if $with_sssd_support { contain 'keycloak::sssd' Class['keycloak::sssd'] ~> Class['keycloak::service'] } keycloak_conn_validator { 'keycloak': keycloak_server => 'localhost', keycloak_port => $http_port, use_ssl => false, timeout => 60, test_url => '/auth/realms/master/.well-known/openid-configuration', require => Class['keycloak::service'], } include keycloak::resources } diff --git a/manifests/truststore/host.pp b/manifests/truststore/host.pp index 8ad68b1..a5e20e3 100644 --- a/manifests/truststore/host.pp +++ b/manifests/truststore/host.pp @@ -1,29 +1,35 @@ # @summary Add host to Keycloak truststore # # @example # keycloak::truststore::host { 'ldap1.example.com': # certificate => '/etc/openldap/certs/0a00000.0', # } # # @param certificate # Path to host certificate # @param ensure # Host ensure value passed to `java_ks` resource. # define keycloak::truststore::host ( String $certificate, Enum['latest', 'present', 'absent'] $ensure = 'latest', ) { include keycloak + if $keycloak::operating_mode == 'domain' { + $_path = "${keycloak::install_base}/domain/configuration/truststore.jks" + } else { + $_path = "${keycloak::install_base}/standalone/configuration/truststore.jks" + } + java_ks { $name: ensure => $ensure, certificate => $certificate, - target => "${keycloak::install_base}/standalone/configuration/truststore.jks", + target => $_path, password => $keycloak::truststore_password, trustcacerts => true, notify => Class['keycloak::service'], } } diff --git a/metadata.json b/metadata.json index 825b58f..800f222 100644 --- a/metadata.json +++ b/metadata.json @@ -1,85 +1,93 @@ { "name": "treydock-keycloak", "version": "6.25.2", "author": "treydock", "summary": "Keycloak Puppet module", "license": "Apache-2.0", "source": "https://github.com/treydock/puppet-module-keycloak", "project_page": "https://github.com/treydock/puppet-module-keycloak", "issues_url": "https://github.com/treydock/puppet-module-keycloak/issues", "dependencies": [ { "name": "puppetlabs/stdlib", "version_requirement": ">= 4.25.0 <7.0.0" }, { "name": "puppetlabs/mysql", "version_requirement": ">= 10.2.0 <11.0.0" }, { "name": "puppetlabs/postgresql", "version_requirement": ">= 6.4.0 <7.0.0" }, { "name": "puppetlabs/java", "version_requirement": ">= 5.0.0 <7.0.0" }, { "name": "puppetlabs/java_ks", "version_requirement": ">= 1.0.0 <4.0.0" }, + { + "name": "puppetlabs/augeas_core", + "version_requirement": ">= 1.0.0 <4.0.0" + }, + { + "name": "puppetlabs/yumrepo_core", + "version_requirement": ">= 1.0.0 <2.0.0" + }, { "name": "puppet/archive", "version_requirement": ">= 0.5.1 <5.0.0" }, { "name": "camptocamp/systemd", "version_requirement": ">= 0.4.0 <3.0.0" } ], "operatingsystem_support": [ { "operatingsystem": "RedHat", "operatingsystemrelease": [ "7", "8" ] }, { "operatingsystem": "CentOS", "operatingsystemrelease": [ "7", "8" ] }, { "operatingsystem": "Scientific", "operatingsystemrelease": [ "7", "8" ] }, { "operatingsystem": "Debian", "operatingsystemrelease": [ "9", "10" ] }, { "operatingsystem": "Ubuntu", "operatingsystemrelease": [ "18.04" ] } ], "requirements": [ { "name": "puppet", "version_requirement": ">= 5.0.0 < 7.0.0" } ], "pdk-version": "1.17.0", "template-url": "https://github.com/treydock/pdk-templates.git#master", "template-ref": "heads/master-0-g1f52e6d" } diff --git a/spec/acceptance/1_class_spec.rb b/spec/acceptance/1_class_spec.rb index 1106fe3..961081c 100644 --- a/spec/acceptance/1_class_spec.rb +++ b/spec/acceptance/1_class_spec.rb @@ -1,159 +1,159 @@ require 'spec_helper_acceptance' -describe 'keycloak class:' do +describe 'keycloak class:', unless: RSpec.configuration.keycloak_domain_mode_cluster do context 'default parameters' do it 'runs successfully' do pp = <<-EOS class { 'keycloak': } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end describe file("/opt/keycloak-#{RSpec.configuration.keycloak_version}") do it { is_expected.to be_directory } end describe service('keycloak') do it { is_expected.to be_enabled } it { is_expected.to be_running } end end context 'default with clustered mode enable' do it 'runs successfully' do pp = <<-EOS class { 'keycloak': operating_mode => 'clustered', } 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 end context 'default with mysql datasource' do it 'runs successfully' do pp = <<-EOS include mysql::server class { 'keycloak': datasource_driver => 'mysql', } 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 end context 'default with postgresql datasource' do it 'runs successfully' do pp = <<-EOS include postgresql::server class { 'keycloak': datasource_driver => 'postgresql', } 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 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 include mysql::server class { 'keycloak': datasource_driver => 'mysql', proxy_https => true, java_opts => '-Xmx512m -Xms64m', } 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 end end diff --git a/spec/acceptance/1_domain_mode_cluster_spec.rb b/spec/acceptance/1_domain_mode_cluster_spec.rb new file mode 100644 index 0000000..60bb589 --- /dev/null +++ b/spec/acceptance/1_domain_mode_cluster_spec.rb @@ -0,0 +1,136 @@ +require 'spec_helper_acceptance' + +# This check needs to be here or Beaker will try to run find_only_one on +# non-domain-mode tests and fail miserably. +if RSpec.configuration.keycloak_domain_mode_cluster + describe 'keycloak domain mode cluster' do + domain_master = find_only_one('domain_master') + domain_slave = find_only_one('domain_slave') + db = find_only_one('db') + + context 'new cluster' do + it 'launches' do + db_pp = <<-EOS + class { '::postgresql::globals': + manage_package_repo => true, + version => '9.6', + } + + class { '::postgresql::server': + listen_addresses => '*', + require => Class['::postgresql::globals'] + } + + ::postgresql::server::role { 'keycloak': + password_hash => postgresql_password('keycloak', 'keycloak'), + connection_limit => 300, + require => Class['::postgresql::server'] + } + + ::postgresql::server::database_grant { 'Grant all to keycloak': + privilege => 'ALL', + db => 'keycloak', + role => 'keycloak', + } + + ::postgresql::server::db { 'keycloak': + user => 'keycloak', + password => postgresql_password('keycloak', 'keycloak'), + } + + postgresql::server::pg_hba_rule { 'Allow Keycloak instances network access to the database': + description => 'Open up PostgreSQL for access from anywhere', + type => 'host', + database => 'keycloak', + user => 'keycloak', + address => '0.0.0.0/0', + auth_method => 'md5', + require => Class['::postgresql::server'] + } + EOS + + master_pp = <<-EOS + class { '::keycloak': + operating_mode => 'domain', + role => 'master', + management_bind_address => $::ipaddress, + enable_jdbc_ping => true, + wildfly_user => 'wildfly', + wildfly_user_password => 'wildfly', + manage_install => true, + manage_datasource => false, + version => '10.0.1', + datasource_driver => 'postgresql', + datasource_host => 'centos-7-db', + datasource_port => 5432, + datasource_dbname => 'keycloak', + datasource_username => 'keycloak', + datasource_password => 'keycloak', + admin_user => 'admin', + admin_user_password => 'changeme', + service_bind_address => '0.0.0.0', + proxy_https => false, + } + EOS + + slave_pp = <<-EOS + class { '::keycloak': + operating_mode => 'domain', + role => 'slave', + enable_jdbc_ping => true, + management_bind_address => $::ipaddress, + wildfly_user => 'wildfly', + wildfly_user_password => 'wildfly', + master_address => 'centos-7-master', + manage_install => true, + manage_datasource => false, + version => '10.0.1', + datasource_driver => 'postgresql', + datasource_host => 'centos-7-db', + datasource_port => 5432, + datasource_dbname => 'keycloak', + datasource_username => 'keycloak', + datasource_password => 'keycloak', + admin_user => 'admin', + admin_user_password => 'changeme', + service_bind_address => '0.0.0.0', + proxy_https => false, + } + EOS + + apply_manifest_on(db, db_pp, catch_failures: true) + apply_manifest_on(domain_master, master_pp, catch_failures: true) + apply_manifest_on(domain_master, master_pp, catch_changes: true) + apply_manifest_on(domain_slave, slave_pp, catch_failures: true) + apply_manifest_on(domain_slave, slave_pp, catch_changes: true) + end + + describe service('keycloak'), node: domain_master do + it { is_expected.to be_enabled } + it { is_expected.to be_running } + end + + describe service('keycloak'), node: domain_slave do + it { is_expected.to be_enabled } + it { is_expected.to be_running } + end + + it 'data replicates from master to slave' do + on domain_master, '/opt/keycloak/bin/kcadm-wrapper.sh create roles -r master -s name=testrole' + on domain_slave, '/opt/keycloak/bin/kcadm-wrapper.sh get roles/testrole -r master' do + data = JSON.parse(stdout) + expect(data['name']).to eq('testrole') + end + end + + it 'data replicates from slave to master' do + on domain_slave, '/opt/keycloak/bin/kcadm-wrapper.sh delete roles/testrole -r master' + on domain_master, '/opt/keycloak/bin/kcadm-wrapper.sh get roles -r master' do + data = JSON.parse(stdout) + match = data.select { |role| role['name'] == 'testrole' } + expect(match).to be_empty + end + end + end + end +end diff --git a/spec/acceptance/nodesets/centos-7-domain-mode-cluster.yml b/spec/acceptance/nodesets/centos-7-domain-mode-cluster.yml new file mode 100644 index 0000000..2ec51e5 --- /dev/null +++ b/spec/acceptance/nodesets/centos-7-domain-mode-cluster.yml @@ -0,0 +1,48 @@ +HOSTS: + centos-7-master: + roles: + - agent + - default + - domain_master + platform: el-7-x86_64 + hypervisor: docker + image: centos:7 + docker_preserve_image: true + docker_cmd: + - '/usr/sbin/init' + docker_image_commands: + - 'yum install -y wget which cronie iproute initscripts' + docker_container_name: 'keycloak-master-el7' + centos-7-slave: + roles: + - agent + - domain_slave + platform: el-7-x86_64 + hypervisor: docker + image: centos:7 + docker_preserve_image: true + docker_cmd: + - '/usr/sbin/init' + docker_image_commands: + - 'yum install -y wget which cronie iproute initscripts' + docker_container_name: 'keycloak-slave-el7' + centos-7-db: + roles: + - agent + - db + platform: el-7-x86_64 + hypervisor: docker + image: centos:7 + docker_preserve_image: true + docker_cmd: + - '/usr/sbin/init' + docker_image_commands: + - 'yum install -y wget which cronie iproute initscripts' + docker_container_name: 'keycloak-db-el7' +CONFIG: + log_level: debug + type: foss +ssh: + password: root + auth_methods: ["password"] + diff --git a/spec/classes/init_spec.rb b/spec/classes/init_spec.rb index a449555..70df8fa 100644 --- a/spec/classes/init_spec.rb +++ b/spec/classes/init_spec.rb @@ -1,199 +1,288 @@ require 'spec_helper' describe 'keycloak' do on_supported_os.each do |os, facts| context "on #{os}" do let(:facts) do facts.merge(concat_basedir: '/dne') end let(:version) { '8.0.1' } case facts[:osfamily] when %r{RedHat} shell = '/sbin/nologin' when %r{Debian} shell = '/usr/sbin/nologin' end it { is_expected.to compile.with_all_deps } it { is_expected.to create_class('keycloak') } it { is_expected.to contain_class('keycloak::install').that_comes_before('Class[keycloak::config]') } it { is_expected.to contain_class('keycloak::config').that_comes_before('Class[keycloak::service]') } it { is_expected.to contain_class('keycloak::service') } + context 'domain master' do + let(:params) do + { + operating_mode: 'domain', + install_dir: '/opt/keycloak-x', + role: 'master', + datasource_driver: 'postgresql', + wildfly_user: 'wildfly', + wildfly_user_password: 'changeme', + } + end + + it { is_expected.to compile.with_all_deps } + it do + is_expected.to contain_augeas('ensure-servername').with(incl: '/opt/keycloak-x/domain/configuration/host-master.xml') + is_expected.to contain_exec('create-wildfly-user').with(command: '/opt/keycloak-x/bin/add-user.sh --user wildfly --password changeme -e -s && touch /opt/keycloak-x/.create-wildfly-user') + end + end + + context 'domain slave' do + let(:params) do + { + operating_mode: 'domain', + install_dir: '/opt/keycloak-x', + role: 'slave', + master_address: '10.0.5.10', + datasource_driver: 'postgresql', + wildfly_user: 'wildfly', + wildfly_user_password: 'changeme', + } + end + + it { is_expected.to compile.with_all_deps } + + it do + is_expected.to contain_augeas('ensure-servername').with(incl: '/opt/keycloak-x/domain/configuration/host-slave.xml', + context: '/files/opt/keycloak-x/domain/configuration/host-slave.xml/host/servers') + is_expected.to contain_exec('create-wildfly-user').with(command: '/opt/keycloak-x/bin/add-user.sh --user wildfly --password changeme -e -s && touch /opt/keycloak-x/.create-wildfly-user') + end + end + + context 'standalone with domain role defined' do + let(:params) do + { + operating_mode: 'standalone', + role: 'master', + } + end + + it { is_expected.not_to compile } + end + + context 'domain slave without master_address' do + let(:params) do + { + operating_mode: 'domain', + wildfly_user: 'wildfly', + wildfly_user_password: 'wildfly', + role: 'slave', + } + end + + it { is_expected.not_to compile } + end + + context 'domain master without wildfly user' do + let(:params) do + { + operating_mode: 'domain', + role: 'master', + wildfly_user_password: 'wildfly', + } + end + + it { is_expected.not_to compile } + end + + context 'domain master without wildfly user password' do + let(:params) do + { + operating_mode: 'domain', + role: 'master', + wildfly_user: 'wildfly', + } + end + + it { is_expected.not_to compile } + end + context 'keycloak::install' do it do is_expected.to contain_user('keycloak').only_with(ensure: 'present', name: 'keycloak', forcelocal: 'true', shell: shell, gid: 'keycloak', home: '/var/lib/keycloak', managehome: 'true', system: 'true') end end context 'keycloak::datasource::mysql' do let(:pre_condition) { 'include ::mysql::server' } let(:params) { { datasource_driver: 'mysql' } } it { is_expected.to contain_class('keycloak::install').that_comes_before('Class[keycloak::datasource::mysql]') } it { is_expected.to contain_class('keycloak::datasource::mysql').that_comes_before('Class[keycloak::config]') } it do is_expected.to contain_mysql__db('keycloak').with(user: 'sa', password: 'sa', host: 'localhost', grant: 'ALL') end context 'manage_datasource => false' do let(:params) { { datasource_driver: 'mysql', manage_datasource: false } } it { is_expected.not_to contain_mysql__db('keycloak') } end end context 'keycloak::datasource::postgresql' do let(:params) { { datasource_driver: 'postgresql' } } it { is_expected.to contain_class('keycloak::install').that_comes_before('Class[keycloak::datasource::postgresql]') } it { is_expected.to contain_class('keycloak::datasource::postgresql').that_comes_before('Class[keycloak::config]') } it do is_expected.to contain_postgresql__server__db('keycloak').with(user: 'sa', password: %r{.*}) end context 'manage_datasource => false' do let(:params) { { datasource_driver: 'postgresql', manage_datasource: false } } it { is_expected.not_to contain_postgresql__server__db('keycloak') } end end context 'keycloak::config' do it do is_expected.to contain_file('kcadm-wrapper.sh').only_with( ensure: 'file', path: "/opt/keycloak-#{version}/bin/kcadm-wrapper.sh", owner: 'keycloak', group: 'keycloak', mode: '0750', content: %r{.*}, show_diff: 'false', ) end it do is_expected.to contain_exec('create-keycloak-admin') .with(command: "/opt/keycloak-#{version}/bin/add-user-keycloak.sh --user admin --password changeme --realm master && touch /opt/keycloak-#{version}/.create-keycloak-admin-h2", creates: "/opt/keycloak-#{version}/.create-keycloak-admin-h2", notify: 'Class[Keycloak::Service]') end it do is_expected.to contain_file("/opt/keycloak-#{version}/standalone/configuration").only_with( ensure: 'directory', owner: 'keycloak', group: 'keycloak', mode: '0750', ) end it do is_expected.to contain_file("/opt/keycloak-#{version}/standalone/configuration/profile.properties").only_with( ensure: 'file', owner: 'keycloak', group: 'keycloak', mode: '0644', content: %r{.*}, notify: 'Class[Keycloak::Service]', ) end it do verify_exact_file_contents(catalogue, "/opt/keycloak-#{version}/standalone/configuration/profile.properties", []) end it do is_expected.to contain_concat("/opt/keycloak-#{version}/config.cli").with( ensure: 'present', owner: 'keycloak', group: 'keycloak', mode: '0600', 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( + is_expected.to contain_file_line('keycloak-JAVA_OPTS').with( ensure: 'absent', path: "/opt/keycloak-#{version}/bin/standalone.conf", line: 'JAVA_OPTS="$JAVA_OPTS "', match: '^JAVA_OPTS=', notify: 'Class[Keycloak::Service]', ) end context 'when tech_preview_features defined' do let(:params) { { tech_preview_features: ['account_api'] } } it do verify_exact_file_contents(catalogue, "/opt/keycloak-#{version}/standalone/configuration/profile.properties", ['feature.account_api=enabled']) end end context 'when java_opts defined' do let(:params) { { java_opts: '-Xmx512m -Xms64m' } } it do - is_expected.to contain_file_line('standalone.conf-JAVA_OPTS').with( + is_expected.to contain_file_line('keycloak-JAVA_OPTS').with( ensure: 'present', path: "/opt/keycloak-#{version}/bin/standalone.conf", line: 'JAVA_OPTS="$JAVA_OPTS -Xmx512m -Xms64m"', match: '^JAVA_OPTS=', notify: 'Class[Keycloak::Service]', ) end context 'when java_opts_append is false' do let(:params) { { java_opts: '-Xmx512m -Xms64m', java_opts_append: false } } it do - is_expected.to contain_file_line('standalone.conf-JAVA_OPTS').with( + is_expected.to contain_file_line('keycloak-JAVA_OPTS').with( ensure: 'present', path: "/opt/keycloak-#{version}/bin/standalone.conf", line: 'JAVA_OPTS="-Xmx512m -Xms64m"', match: '^JAVA_OPTS=', notify: 'Class[Keycloak::Service]', ) end end end end context 'keycloak::service' do it do is_expected.to contain_service('keycloak').only_with(ensure: 'running', enable: 'true', name: 'keycloak', hasstatus: 'true', hasrestart: 'true') end end end # end context end # end on_supported_os loop end # end describe diff --git a/spec/spec_helper_acceptance_setup.rb b/spec/spec_helper_acceptance_setup.rb index c10b844..b96db6c 100644 --- a/spec/spec_helper_acceptance_setup.rb +++ b/spec/spec_helper_acceptance_setup.rb @@ -1,42 +1,44 @@ RSpec.configure do |c| c.add_setting :keycloak_version keycloak_version = if ENV['BEAKER_keycloak_version'].nil? || ENV['BEAKER_keycloak_version'].empty? '8.0.1' else ENV['BEAKER_keycloak_version'] end c.keycloak_version = keycloak_version c.add_setting :keycloak_full c.keycloak_full = (ENV['BEAKER_keycloak_full'] == 'true' || ENV['BEAKER_keycloak_full'] == 'yes') + c.add_setting :keycloak_domain_mode_cluster + c.keycloak_domain_mode_cluster = (ENV['BEAKER_keycloak_domain_mode_cluster'] == 'true' || ENV['BEAKER_keycloak_domain_mode_cluster'] == 'yes') end proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) scp_to(hosts, File.join(proj_root, 'spec/fixtures/keycloak-duo-spi-jar-with-dependencies.jar'), '/tmp/keycloak-duo-spi-jar-with-dependencies.jar') hiera_yaml = <<-EOS --- version: 5 defaults: datadir: data data_hash: yaml_data hierarchy: - name: 'os family major release' path: "os/%{facts.os.family}/%{facts.os.release.major}.yaml" - name: "Common" path: "common.yaml" EOS # Hack until released: https://github.com/puppetlabs/puppetlabs-mysql/pull/1264 debian10_yaml = <<-EOS mysql::bindings::java_package_name: libmariadb-java EOS common_yaml = <<-EOS --- keycloak::version: '#{RSpec.configuration.keycloak_version}' postgresql::globals::service_status: 'service postgresql status' EOS create_remote_file(hosts, '/etc/puppetlabs/puppet/hiera.yaml', hiera_yaml) on hosts, 'mkdir -p /etc/puppetlabs/puppet/data' create_remote_file(hosts, '/etc/puppetlabs/puppet/data/common.yaml', common_yaml) on hosts, 'mkdir -p /etc/puppetlabs/puppet/data/os/Debian' create_remote_file(hosts, '/etc/puppetlabs/puppet/data/os/Debian/10.yaml', debian10_yaml) diff --git a/templates/config.cli.erb b/templates/config.cli.erb index 91e74b0..e8e5308 100644 --- a/templates/config.cli.erb +++ b/templates/config.cli.erb @@ -1,119 +1,218 @@ <% if scope['keycloak::operating_mode'] == 'standalone'-%> embed-server +<% @prefix=''-%> <% elsif scope['keycloak::operating_mode'] == 'clustered'-%> embed-server --server-config=standalone-ha.xml +<% @prefix=''-%> +<% else -%> +embed-host-controller +<% @prefix='/profile=auth-server-clustered'-%> <% end -%> -<%- if scope['keycloak::proxy_https'] -%> -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) + +<%- # https proxying -%> +<%- if scope['keycloak::proxy_https'] -%> +if (result.proxy-address-forwarding != true) of <%= @prefix -%>/subsystem=undertow/server=default-server/http-listener=default:read-resource +<%= @prefix -%>/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) +if (result.proxy-address-forwarding != true) of <%= @prefix -%>/subsystem=undertow/server=default-server/https-listener=https:read-resource +<%= @prefix -%>/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) +if (outcome != success) of <%= @prefix -%>/socket-binding-group=standard-sockets/socket-binding=proxy-https:read-resource +<%= @prefix -%>/socket-binding-group=standard-sockets/socket-binding=proxy-https:add(port=443) end-if -if (result.redirect-socket != proxy-https) of /subsystem=undertow/server=default-server/http-listener=default:read-resource -/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=redirect-socket,value=proxy-https) +if (result.redirect-socket != proxy-https) of <%= @prefix -%>/subsystem=undertow/server=default-server/http-listener=default:read-resource +<%= @prefix -%>/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=redirect-socket,value=proxy-https) end-if <%- end -%> -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=driver-name, value=<%= scope['keycloak::datasource_driver'] %>) -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=connection-url, value="<%= scope['keycloak::datasource_connection_url'] %>") -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=jndi-name, value=java:jboss/datasources/KeycloakDS) -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=user-name, value=<%= scope['keycloak::datasource_username'] %>) -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=password, value=<%= scope['keycloak::datasource_password'] %>) + +<%- # datasources -%> +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=driver-name, value=<%= scope['keycloak::datasource_driver'] %>) +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=connection-url, value="<%= scope['keycloak::datasource_connection_url'] %>") +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=jndi-name, value=java:jboss/datasources/KeycloakDS) +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=user-name, value=<%= scope['keycloak::datasource_username'] %>) +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=password, value=<%= scope['keycloak::datasource_password'] %>) <%- if scope['keycloak::datasource_driver'] == 'mysql' -%> -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=background-validation, value=true) -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=check-valid-connection-sql, value="SELECT 1") -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=background-validation-millis, value=60000) -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=flush-strategy, value=IdleConnections) +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=background-validation, value=true) +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=check-valid-connection-sql, value="SELECT 1") +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=background-validation-millis, value=60000) +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=flush-strategy, value=IdleConnections) try -/subsystem=datasources/jdbc-driver=mysql:add(driver-module-name=com.mysql.jdbc,driver-name=mysql,driver-xa-datasource-class-name=<%= scope['keycloak::mysql_datasource_class'] %>) +<%= @prefix -%>/subsystem=datasources/jdbc-driver=mysql:add(driver-module-name=com.mysql.jdbc,driver-name=mysql,driver-xa-datasource-class-name=<%= scope['keycloak::mysql_datasource_class'] %>) catch -/subsystem=datasources/jdbc-driver=mysql:remove -/subsystem=datasources/jdbc-driver=mysql:add(driver-module-name=com.mysql.jdbc,driver-name=mysql,driver-xa-datasource-class-name=<%= scope['keycloak::mysql_datasource_class'] %>) +<%= @prefix -%>/subsystem=datasources/jdbc-driver=mysql:remove +<%= @prefix -%>/subsystem=datasources/jdbc-driver=mysql:add(driver-module-name=com.mysql.jdbc,driver-name=mysql,driver-xa-datasource-class-name=<%= scope['keycloak::mysql_datasource_class'] %>) end-try <%- elsif scope['keycloak::datasource_driver'] == 'h2' -%> /subsystem=datasources/data-source=KeycloakDS:undefine-attribute(name=background-validation) /subsystem=datasources/data-source=KeycloakDS:undefine-attribute(name=check-valid-connection-sql) /subsystem=datasources/data-source=KeycloakDS:undefine-attribute(name=background-validation-millis) /subsystem=datasources/data-source=KeycloakDS:undefine-attribute(name=flush-strategy) <%- elsif scope['keycloak::datasource_driver'] == 'oracle' -%> -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=background-validation, value=true) -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=check-valid-connection-sql, value="SELECT 1 FROM DUAL") -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=background-validation-millis, value=60000) -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=flush-strategy, value=IdleConnections) +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=background-validation, value=true) +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=check-valid-connection-sql, value="SELECT 1 FROM DUAL") +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=background-validation-millis, value=60000) +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=flush-strategy, value=IdleConnections) try -/subsystem=datasources/jdbc-driver=oracle:add(driver-module-name=org.oracle,driver-name=oracle,driver-xa-datasource-class-name=oracle.jdbc.xa.client.OracleXADataSource) +<%= @prefix -%>/subsystem=datasources/jdbc-driver=oracle:add(driver-module-name=org.oracle,driver-name=oracle,driver-xa-datasource-class-name=oracle.jdbc.xa.client.OracleXADataSource) catch -/subsystem=datasources/jdbc-driver=oracle:remove -/subsystem=datasources/jdbc-driver=oracle:add(driver-module-name=org.oracle,driver-name=oracle,driver-xa-datasource-class-name=oracle.jdbc.xa.client.OracleXADataSource) +<%= @prefix -%>/subsystem=datasources/jdbc-driver=oracle:remove +<%= @prefix -%>/subsystem=datasources/jdbc-driver=oracle:add(driver-module-name=org.oracle,driver-name=oracle,driver-xa-datasource-class-name=oracle.jdbc.xa.client.OracleXADataSource) end-try <%- elsif scope['keycloak::datasource_driver'] == 'postgresql' -%> -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=background-validation, value=true) -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=check-valid-connection-sql, value="SELECT 1") -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=background-validation-millis, value=60000) -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=flush-strategy, value=IdleConnections) +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=background-validation, value=true) +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=check-valid-connection-sql, value="SELECT 1") +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=background-validation-millis, value=60000) +<%= @prefix -%>/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=flush-strategy, value=IdleConnections) try -/subsystem=datasources/jdbc-driver=postgresql:add(driver-module-name=org.postgresql,driver-name=postgresql,driver-xa-datasource-class-name=org.postgresql.xa.PGXADataSource) +<%= @prefix -%>/subsystem=datasources/jdbc-driver=postgresql:add(driver-module-name=org.postgresql,driver-name=postgresql,driver-xa-datasource-class-name=org.postgresql.xa.PGXADataSource) catch -/subsystem=datasources/jdbc-driver=postgresql:remove -/subsystem=datasources/jdbc-driver=postgresql:add(driver-module-name=org.postgresql,driver-name=postgresql,driver-xa-datasource-class-name=org.postgresql.xa.PGXADataSource) +<%= @prefix -%>/subsystem=datasources/jdbc-driver=postgresql:remove +<%= @prefix -%>/subsystem=datasources/jdbc-driver=postgresql:add(driver-module-name=org.postgresql,driver-name=postgresql,driver-xa-datasource-class-name=org.postgresql.xa.PGXADataSource) end-try <%- end -%> + +<%- # truststore -%> <%- if scope['keycloak::truststore'] -%> -if (outcome != success) of /subsystem=keycloak-server/spi=truststore:read-resource -/subsystem=keycloak-server/spi=truststore/:add -/subsystem=keycloak-server/spi=truststore/provider=file/:add(enabled=true) -end-if -/subsystem=keycloak-server/spi=truststore/provider=file/:map-put(name=properties,key=file,value=<%= scope['keycloak::install_base'] %>/standalone/configuration/truststore.jks) -/subsystem=keycloak-server/spi=truststore/provider=file/:map-put(name=properties,key=password,value=<%= scope['keycloak::truststore_password'] %>) -/subsystem=keycloak-server/spi=truststore/provider=file/:map-put(name=properties,key=hostname-verification-policy,value=<%= scope['keycloak::truststore_hostname_verification_policy'] %>) -/subsystem=keycloak-server/spi=truststore/provider=file/:map-put(name=properties,key=disabled,value=false) +if (outcome != success) of <%= @prefix -%>/subsystem=keycloak-server/spi=truststore:read-resource +<%= @prefix -%>/subsystem=keycloak-server/spi=truststore/:add +<%= @prefix -%>/subsystem=keycloak-server/spi=truststore/provider=file/:add(enabled=true) +end-if +<% if scope['keycloak::operating_mode'] == 'domain'-%> +<%= @prefix -%>/subsystem=keycloak-server/spi=truststore/provider=file/:map-put(name=properties,key=file,value=<%= scope['keycloak::install_base'] %>/domain/configuration/truststore.jks) +<% else -%> +<%= @prefix -%>/subsystem=keycloak-server/spi=truststore/provider=file/:map-put(name=properties,key=file,value=<%= scope['keycloak::install_base'] %>/standalone/configuration/truststore.jks) +<% end -%> +<%= @prefix -%>/subsystem=keycloak-server/spi=truststore/provider=file/:map-put(name=properties,key=password,value=<%= scope['keycloak::truststore_password'] %>) +<%= @prefix -%>/subsystem=keycloak-server/spi=truststore/provider=file/:map-put(name=properties,key=hostname-verification-policy,value=<%= scope['keycloak::truststore_hostname_verification_policy'] %>) +<%= @prefix -%>/subsystem=keycloak-server/spi=truststore/provider=file/:map-put(name=properties,key=disabled,value=false) <%- else -%> -if (outcome == success) of /subsystem=keycloak-server/spi=truststore:read-resource -/subsystem=keycloak-server/spi=truststore/:remove +if (outcome == success) of <%= @prefix -%>/subsystem=keycloak-server/spi=truststore:read-resource +<%= @prefix -%>/subsystem=keycloak-server/spi=truststore/:remove end-if <%- end -%> -/subsystem=keycloak-server/theme=defaults/:write-attribute(name=staticMaxAge, value=<%= scope['keycloak::theme_static_max_age'] %>) -/subsystem=keycloak-server/theme=defaults/:write-attribute(name=cacheThemes, value=<%= scope['keycloak::theme_cache_themes'] %>) -/subsystem=keycloak-server/theme=defaults/:write-attribute(name=cacheTemplates, value=<%= scope['keycloak::theme_cache_templates'] %>) -/subsystem=deployment-scanner/scanner=default:write-attribute(name="auto-deploy-exploded",value=<%= scope['keycloak::auto_deploy_exploded'] %>) -/subsystem=deployment-scanner/scanner=default:write-attribute(name="auto-deploy-zipped",value=<%= scope['keycloak::auto_deploy_zipped'] %>) + +<%- # theming -%> +<%= @prefix -%>/subsystem=keycloak-server/theme=defaults/:write-attribute(name=staticMaxAge, value=<%= scope['keycloak::theme_static_max_age'] %>) +<%= @prefix -%>/subsystem=keycloak-server/theme=defaults/:write-attribute(name=cacheThemes, value=<%= scope['keycloak::theme_cache_themes'] %>) +<%= @prefix -%>/subsystem=keycloak-server/theme=defaults/:write-attribute(name=cacheTemplates, value=<%= scope['keycloak::theme_cache_templates'] %>) + +<%- # deployment scanner is not compatible with domain mode -%> +<% if scope['keycloak::operating_mode'] != 'domain'-%> +<%= @prefix -%>/subsystem=deployment-scanner/scanner=default:write-attribute(name="auto-deploy-exploded",value=<%= scope['keycloak::auto_deploy_exploded'] %>) +<%= @prefix -%>/subsystem=deployment-scanner/scanner=default:write-attribute(name="auto-deploy-zipped",value=<%= scope['keycloak::auto_deploy_zipped'] %>) +<% end -%> + try -/subsystem=keycloak-server/spi=userCache/provider=default/:add(enabled=<%= scope['keycloak::user_cache']%>) +<%= @prefix -%>/subsystem=keycloak-server/spi=userCache/provider=default/:add(enabled=<%= scope['keycloak::user_cache']%>) catch -/subsystem=keycloak-server/spi=userCache/provider=default/:remove -/subsystem=keycloak-server/spi=userCache/provider=default/:add(enabled=<%= scope['keycloak::user_cache']%>) +<%= @prefix -%>/subsystem=keycloak-server/spi=userCache/provider=default/:remove +<%= @prefix -%>/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 + +<%- # JDBC_PING & remove udp stack -%> +<%- if scope['keycloak::operating_mode'] != 'standalone' && scope['keycloak::enable_jdbc_ping'] -%> +if (outcome != success) of <%= @prefix -%>/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))"]) +<%= @prefix -%>/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"]) +<%= @prefix -%>/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() +if (outcome == success) of <%= @prefix -%>/subsystem=jgroups/stack=tcp/protocol=MPING:read-resource +<%= @prefix -%>/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]) +if (outcome == success) of <%= @prefix -%>/subsystem=jgroups/stack=tcp/protocol=pbcast.GMS:read-resource +<%= @prefix -%>/subsystem=jgroups/stack=tcp/protocol=pbcast.GMS: remove() +<%= @prefix -%>/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 +if (outcome != success) of <%= @prefix -%>/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() +<%= @prefix -%>/subsystem=jgroups/channel=ee:write-attribute(name=stack, value="tcp") +if (outcome == success) of <%= @prefix -%>/subsystem=jgroups/stack=udp:read-resource +<%= @prefix -%>/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() +if (outcome == success) of <%= @prefix -%>/socket-binding-group=standard-sockets/socket-binding=jgroups-udp:read-resource +<%= @prefix -%>/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() +if (outcome == success) of <%= @prefix -%>/socket-binding-group=standard-sockets/socket-binding=jgroups-mping:read-resource +<%= @prefix -%>/socket-binding-group=standard-sockets/socket-binding=jgroups-mping:remove() end-if +<%- if scope['keycloak::operating_mode'] != 'domain' -%> /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 -%> +<%- end -%> + +<%- # domain mode specific things -%> +<% if scope['keycloak::operating_mode'] == 'domain' -%> + +<%- # remove load balancer -%> +if (outcome == success) of /host=master/server-config=load-balancer:read-resource +/host=master/server-config=load-balancer:remove +end-if +if (outcome == success) of /server-group=load-balancer-group:read-resource +/server-group=load-balancer-group:remove +end-if +if (outcome == success) of /profile=load-balancer:read-resource +/profile=load-balancer:remove +end-if +if (outcome == success) of /socket-binding-group=load-balancer-sockets:read-resource +/socket-binding-group=load-balancer-sockets:remove +end-if + +<%- # ensure proxying for ha sockets -%> +if (outcome != success) of /socket-binding-group=ha-sockets/socket-binding=proxy-https:read-resource +/socket-binding-group=ha-sockets/socket-binding=proxy-https:add(port=443) +end-if + +<%- # caches -%> +<%= @prefix -%>/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:2}) +<%= @prefix -%>/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:2}) +<%= @prefix -%>/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:2}) +<%= @prefix -%>/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:2}) +<%= @prefix -%>/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:2}) +<%= @prefix -%>/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:write-attribute(name=owners, value=${env.CACHE_OWNERS:2}) +<%= @prefix -%>/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:write-attribute(name=owners, value=${env.CACHE_OWNERS:2}) + +<%- # take control of the interfaces -%> +if (outcome != success) of /interface=management:read-resource() +/interface=management:add() +end-if +if (result != undefined) of /interface=management:read-attribute(name=inet-address) +/interface=management:write-attribute(name=inet-address, value=undefined) +end-if +if (outcome != success) of /interface=private:read-resource() +/interface=private:add() +end-if +if (result != undefined) of /interface=private:read-attribute(name=inet-address) +/interface=private:write-attribute(name=inet-address, value=undefined) +end-if +if (outcome != success) of /interface=public:read-resource() +/interface=public:add() +end-if +if (result != undefined) of /interface=public:read-attribute(name=inet-address) +/interface=public:write-attribute(name=inet-address, value=undefined) +end-if +if (result != public) of /socket-binding-group=ha-sockets:read-attribute(name=default-interface) +/socket-binding-group=ha-sockets:write-attribute(name=default-interface, value=public) +end-if +if (result != defined) of /socket-binding-group=ha-sockets/socket-binding=ajp:read-attribute(name=interface) +/socket-binding-group=ha-sockets/socket-binding=ajp:write-attribute(name=interface, value=undefined) +end-if +if (result != defined) of /socket-binding-group=ha-sockets/socket-binding=http:read-attribute(name=interface) +/socket-binding-group=ha-sockets/socket-binding=http:write-attribute(name=interface, value=undefined) +end-if +if (result != defined) of /socket-binding-group=ha-sockets/socket-binding=https:read-attribute(name=interface) +/socket-binding-group=ha-sockets/socket-binding=https:write-attribute(name=interface, value=undefined) +end-if +if (result != management) of /socket-binding-group=ha-sockets/socket-binding=jgroups-tcp:read-attribute(name=interface) +/socket-binding-group=ha-sockets/socket-binding=jgroups-tcp:write-attribute(name=interface,value=management) +end-if + +<%- # ensure datasource for ee default bindings is correct -%> +if (result != java:jboss/datasources/KeycloakDS) of <%= @prefix -%>/subsystem=ee/service=default-bindings:read-attribute(name=datasource) +<%= @prefix -%>/subsystem=ee/service=default-bindings:write-attribute(name=datasource,value=java:jboss/datasources/KeycloakDS) +end-if +stop-embedded-host-controller +<% end -%> diff --git a/templates/keycloak.service.erb b/templates/keycloak.service.erb index aab12de..0392f2c 100644 --- a/templates/keycloak.service.erb +++ b/templates/keycloak.service.erb @@ -1,22 +1,28 @@ [Unit] Description=Jboss Application Server After=network.target [Service] Type=idle SyslogIdentifier=keycloak <% if scope['keycloak::service_environment_file'] -%> EnvironmentFile=<%= scope['keycloak::service_environment_file'] %> <% end -%> User=<%= scope['keycloak::user'] %> Group=<%= scope['keycloak::group'] %> <% if scope['keycloak::operating_mode'] == 'standalone'-%> ExecStart=<%= scope['keycloak::install_base'] %>/bin/standalone.sh -b <%= scope['keycloak::service_bind_address'] %> -Djboss.http.port=<%= scope['keycloak::http_port'] %><% if scope['keycloak::service_extra_opts'] -%> <%= scope['keycloak::service_extra_opts'] -%><% end %> <% elsif scope['keycloak::operating_mode'] == 'clustered'-%> ExecStart=<%= scope['keycloak::install_base'] %>/bin/standalone.sh --server-config=standalone-ha.xml -b <%= scope['keycloak::service_bind_address'] %> -Djboss.http.port=<%= scope['keycloak::http_port'] %><% if scope['keycloak::service_extra_opts'] -%> <%= scope['keycloak::service_extra_opts'] -%><% end %> +<% elsif scope['keycloak::operating_mode'] == 'domain'-%> +<% if scope['keycloak::role'] == 'master' -%> +ExecStart=<%= scope['keycloak::install_base'] %>/bin/domain.sh --host-config=host-master.xml -b <%= scope['keycloak::service_bind_address'] %> -Djboss.http.port=<%= scope['keycloak::http_port'] %> -Djboss.bind.address.management=<%= scope['keycloak::management_bind_address'] %> <% if scope['keycloak::service_extra_opts'] -%> <%= scope['keycloak::service_extra_opts'] -%><% end %> +<% else -%> +ExecStart=<%= scope['keycloak::install_base'] %>/bin/domain.sh --host-config=host-slave.xml -b <%= scope['keycloak::service_bind_address'] %> -Djboss.http.port=<%= scope['keycloak::http_port'] %> -Djboss.domain.master.address=<%= scope['keycloak::master_address'] %> -Djboss.bind.address.management=<%= scope['keycloak::management_bind_address'] %> <% if scope['keycloak::service_extra_opts'] -%> <%= scope['keycloak::service_extra_opts'] -%><% end %> +<% end -%> <% end -%> TimeoutStartSec=600 TimeoutStopSec=600 [Install] WantedBy=multi-user.target diff --git a/vagrant/Puppetfile b/vagrant/Puppetfile new file mode 100644 index 0000000..d09d55b --- /dev/null +++ b/vagrant/Puppetfile @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby +#^syntax detection + +forge "https://forgeapi.puppetlabs.com" + +mod 'puppetlabs-stdlib' +mod 'puppetfinland-easy_ipa', + :git => 'https://github.com/Puppet-Finland/puppet-ipa.git', + :commit => '67874ab7f40e4643b77adfd4155f9eb494776bc8' +mod 'puppetlabs-mysql' +mod 'puppetlabs-java' +mod 'puppetlabs-java_ks' +mod 'puppet-archive' +mod 'camptocamp-systemd' +mod 'puppetlabs-concat' +mod 'puppetlabs-apt' +mod 'puppetlabs-postgresql' +mod 'puppetlabs-cron_core' +mod 'puppetlabs-inifile' +mod 'puppetlabs-k5login_core' +mod 'puppetlabs-resource_api' +mod 'puppetlabs-translate' +mod 'puppetlabs-puppetserver_gem' +mod 'puppetlabs-haproxy' diff --git a/vagrant/Vagrantfile b/vagrant/Vagrantfile new file mode 100644 index 0000000..b34252b --- /dev/null +++ b/vagrant/Vagrantfile @@ -0,0 +1,107 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure(2) do |config| + config.hostmanager.enabled = true + config.hostmanager.manage_host = true + config.hostmanager.manage_guest = true + config.hostmanager.ignore_private_ip = false + config.hostmanager.include_offline = false + + config.vm.define "db" do |box| + box.vm.box = "centos/7" + box.vm.hostname = 'db.local' + box.vm.synced_folder "..", "/vagrant", type: "virtualbox" + box.hostmanager.manage_guest = true + box.hostmanager.aliases = %w(db) + box.vm.network "private_network", ip: "192.168.168.254" + box.vm.provider 'virtualbox' do |vb| + vb.linked_clone = true + vb.gui = false + vb.memory = 1024 + vb.customize ["modifyvm", :id, "--ioapic", "on"] + vb.customize ["modifyvm", :id, "--hpet", "on"] + vb.customize ["modifyvm", :id, "--audio", "none"] + end + box.vm.provision "shell" do |s| + s.path = "install_agent.sh" + end + box.vm.provision "shell" do |s| + s.path = "run_puppet.sh" + s.args = ["-b", "/vagrant", "-m", "prepare.pp db.pp" ] + end + end + + config.vm.define "master" do |box| + box.vm.box = "centos/7" + box.vm.hostname = 'master.local' + box.vm.synced_folder "..", "/vagrant", type: "virtualbox" + box.hostmanager.manage_guest = true + box.hostmanager.aliases = %w(master) + box.vm.network "private_network", ip: "192.168.168.253" + box.vm.provider 'virtualbox' do |vb| + vb.linked_clone = true + vb.gui = false + vb.memory = 1024 + vb.customize ["modifyvm", :id, "--ioapic", "on"] + vb.customize ["modifyvm", :id, "--hpet", "on"] + vb.customize ["modifyvm", :id, "--audio", "none"] + end + box.vm.provision "shell" do |s| + s.path = "install_agent.sh" + end + box.vm.provision "shell" do |s| + s.path = "run_puppet.sh" + s.args = ["-b", "/vagrant", "-m", "prepare.pp master.pp"] + end + end + + config.vm.define "slave" do |box| + box.vm.box = "centos/7" + box.vm.hostname = 'slave.local' + box.vm.synced_folder "..", "/vagrant", type: "virtualbox" + box.hostmanager.manage_guest = true + box.hostmanager.aliases = %w(slave) + box.vm.network "private_network", ip: "192.168.168.252" + box.vm.provider 'virtualbox' do |vb| + vb.linked_clone = true + vb.gui = false + vb.memory = 1024 + vb.customize ["modifyvm", :id, "--ioapic", "on"] + vb.customize ["modifyvm", :id, "--hpet", "on"] + vb.customize ["modifyvm", :id, "--audio", "none"] + end + box.vm.provision "shell" do |s| + s.path = "install_agent.sh" + end + box.vm.provision "shell" do |s| + s.path = "run_puppet.sh" + s.args = ["-b", "/vagrant", "-m", "prepare.pp slave.pp"] + end + end + + config.vm.define "lb" do |box| + box.vm.box = "centos/7" + box.vm.hostname = 'lb.local' + box.vm.synced_folder "..", "/vagrant", type: "virtualbox" + box.hostmanager.manage_guest = true + box.hostmanager.aliases = %w(lb) + box.vm.network "private_network", ip: "192.168.168.251" + box.vm.provider 'virtualbox' do |vb| + vb.linked_clone = true + vb.gui = false + vb.memory = 1024 + vb.customize ["modifyvm", :id, "--ioapic", "on"] + vb.customize ["modifyvm", :id, "--hpet", "on"] + vb.customize ["modifyvm", :id, "--audio", "none"] + end + box.vm.provision "shell" do |s| + s.path = "install_agent.sh" + end + box.vm.provision "shell" do |s| + s.path = "run_puppet.sh" + s.args = ["-b", "/vagrant", "-m", "prepare.pp lb.pp"] + end + end +end + diff --git a/vagrant/db.pp b/vagrant/db.pp new file mode 100644 index 0000000..b9fb0fa --- /dev/null +++ b/vagrant/db.pp @@ -0,0 +1,36 @@ +class { '::postgresql::globals': + manage_package_repo => $manage_package_repo, + version => $postgresql_version, +} + +class { '::postgresql::server': + listen_addresses => $postgresql_listen_address, + require => Class['::postgresql::globals'] +} + +::postgresql::server::role { $db_username: + password_hash => postgresql_password($db_username, $db_password), + connection_limit => $db_connection_limit, + require => Class['::postgresql::server'] +} + +::postgresql::server::database_grant { "Grant all to ${db_username}": + privilege => 'ALL', + db => $db_database, + role => $db_username, +} + +::postgresql::server::db { $db_database: + user => $db_username, + password => postgresql_password($db_username, $db_password), +} + +postgresql::server::pg_hba_rule { 'Allow Keycloak instances network access to the database': + description => 'Open up PostgreSQL for access from 192.168.168.0/24', + type => 'host', + database => $db_username, + user => $db_password, + address => '192.168.168.0/24', + auth_method => 'md5', + require => Class['::postgresql::server'] +} diff --git a/vagrant/install_agent.sh b/vagrant/install_agent.sh new file mode 100755 index 0000000..19b70e9 --- /dev/null +++ b/vagrant/install_agent.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +# Exit on any error +set -e + +CWD=`pwd` + +detect_osfamily() { + if [ -f /etc/redhat-release ]; then + OSFAMILY='redhat' + RELEASE=$(cat /etc/redhat-release) + if [ "`echo $RELEASE | grep -E 7\.[0-9]+`" ]; then + RHEL_VERSION="7" + else + echo "Unsupported Redhat/Centos version. Supported versions are 7.x" + exit 1 + fi + elif [ "`lsb_release -d | grep -E '(Ubuntu|Debian)'`" ]; then + OSFAMILY='debian' + DESCR="$(lsb_release -d | awk '{ print $2}')" + if [ `echo $DESCR|grep Ubuntu` ]; then + UBUNTU_VERSION="$(lsb_release -c | awk '{ print $2}')" + elif [ `echo $DESCR|grep Debian` ]; then + DEBIAN_VERSION="$(lsb_release -c | awk '{ print $2}')" + else + echo "Unsupported Debian family operating system. Supported are Debian and Ubuntu" + exit 1 + fi + else + echo "ERROR: unsupported osfamily. Supported are Debian and RedHat" + exit 1 + fi +} + +setup_puppet() { + if [ -x /opt/puppetlabs/bin/puppet ]; then + true + else + if [ $RHEL_VERSION ]; then + RELEASE_URL="https://yum.puppetlabs.com/puppet6/puppet6-release-el-${RHEL_VERSION}.noarch.rpm" + rpm -hiv "${RELEASE_URL}" || (c=$?; echo "Failed to install ${RELEASE_URL}"; (exit $c)) + yum -y install puppet-agent || (c=$?; echo "Failed to install puppet agent"; (exit $c)) + if systemctl list-unit-files --type=service | grep firewalld; then + systemctl stop firewalld + systemctl disable firewalld + systemctl mask firewalld + fi + else + if [ $UBUNTU_VERSION ]; then + APT_URL="https://apt.puppetlabs.com/puppet6-release-${UBUNTU_VERSION}.deb" + fi + if [ $DEBIAN_VERSION ]; then + APT_URL="https://apt.puppetlabs.com/puppet6-release-${DEBIAN_VERSION}.deb" + fi + # https://serverfault.com/questions/500764/dpkg-reconfigure-unable-to-re-open-stdin-no-file-or-directory + export DEBIAN_FRONTEND=noninteractive + FILE="$(mktemp -d)/puppet-release.db" + wget "${APT_URL}" -qO $FILE || (c=$?; echo "Failed to retrieve ${APT_URL}"; (exit $c)) + dpkg --install $FILE; rm $FILE; apt-get update || (c=$?; echo "Failed to install from ${FILE}"; (exit $c)) + apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -y install puppet-agent || (c=$?; echo "Failed to install puppet agent"; (exit $c)) + fi + fi +} + +# Main program +detect_osfamily +setup_puppet + +cd $CWD diff --git a/vagrant/lb.pp b/vagrant/lb.pp new file mode 100644 index 0000000..3be769c --- /dev/null +++ b/vagrant/lb.pp @@ -0,0 +1,38 @@ +notify { 'Installing Load Balancer': } + +include ::haproxy + +haproxy::listen { 'kc': + collect_exported => false, + ipaddress => $facts['networking']['interfaces']['eth1']['ip'], + mode => 'http', + ports => '80', + options => { + 'option' => [ + 'tcplog', + 'forwardfor', + 'http-keep-alive' + ], + 'balance' => 'roundrobin', + 'cookie' => 'SRVNAME insert', + 'http-request' => 'set-header X-Forwarded-Port %[dst_port]', + }, +} + +haproxy::balancermember { 'master': + listening_service => 'kc', + server_names => 'master.local', + ipaddresses => '192.168.168.253', + ports => '8080', + options => 'cookie DC check', +} + +haproxy::balancermember { 'slave': + listening_service => 'kc', + server_names => 'slave.local', + ipaddresses => '192.168.168.252', + ports => '8080', + options => 'cookie HC check', +} + + diff --git a/vagrant/master.pp b/vagrant/master.pp new file mode 100644 index 0000000..0a35ad4 --- /dev/null +++ b/vagrant/master.pp @@ -0,0 +1,50 @@ +notify { 'Installing Master': } + +class { '::keycloak': + operating_mode => 'domain', + role => 'master', + management_bind_address => '192.168.168.253', + enable_jdbc_ping => true, + wildfly_user => $keycloak_wildfly_user, + wildfly_user_password => $keycloak_wildfly_user_password, + manage_install => true, + manage_datasource => false, + version => $keycloak_version, + datasource_driver => 'postgresql', + datasource_host => $keycloak_datasource_host, + datasource_port => 5432, + datasource_dbname => $keycloak_datasource_dbname, + datasource_username => $keycloak_datasource_username, + datasource_password => $keycloak_datasource_password, + admin_user => $keycloak_admin_user, + admin_user_password => $keycloak_admin_user_password, + service_bind_address => '0.0.0.0', + proxy_https => false, +} + +keycloak_realm { 'TEST.NET': + ensure => 'present', + display_name => 'TEST.NET', + display_name_html => 'TEST.NET', + login_with_email_allowed => false, + remember_me => false, + events_enabled => true, + admin_events_enabled => true, + admin_events_details_enabled => true, +} + +keycloak_client { 'example.com': + ensure => 'present', + realm => 'TEST.NET', + standard_flow_enabled => true, + protocol => 'saml', + full_scope_allowed => true, + service_accounts_enabled => false, + base_url => 'https://example.com/', + redirect_uris => [ + 'https://example.com/', + 'https://example.com/*', + ], + require => Keycloak_realm['TEST.NET'], +} + diff --git a/vagrant/prepare.pp b/vagrant/prepare.pp new file mode 100644 index 0000000..2c6d655 --- /dev/null +++ b/vagrant/prepare.pp @@ -0,0 +1,28 @@ +notify { 'Preparing for setup': } + +$tools = [ 'tcpdump', 'strace', 'nmap', 'screen', 'net-tools' ] + +package { $tools: + ensure => 'installed', +} + +package { 'r10k': + ensure => 'present', + provider => 'puppet_gem', +} + +package { 'git': + ensure => 'latest', +} + +exec { 'Update modules': + logoutput => true, + command => "r10k puppetfile install --puppetfile ${::basedir}/vagrant/Puppetfile --verbose --moduledir /etc/puppetlabs/code/environments/production/modules", # lint:ignore:140chars + timeout => 600, + path => ['/bin','/usr/bin','/opt/puppetlabs/bin','/opt/puppetlabs/puppet/bin'], +} + +file { '/etc/puppetlabs/code/environments/production/modules/keycloak': + ensure => 'link', + target => $::basedir, +} diff --git a/vagrant/run_puppet.sh b/vagrant/run_puppet.sh new file mode 100755 index 0000000..0c1f2f4 --- /dev/null +++ b/vagrant/run_puppet.sh @@ -0,0 +1,72 @@ +#!/bin/sh + +# Exit on any error +set -e + +# Preparations required prior to "puppet apply". + +usage() { + echo + echo "Usage: run_puppet.sh -b basedir" + echo + echo "Options:" + echo " -b Base directory for dependency Puppet modules installed by" + echo " librarian-puppet." + echo " -m Puppet manifests to run. Put them in the provision folder" + echo " -d Turn on debugging" + exit 1 +} + +# Parse the options + +# We are run without parameters -> usage +if [ "$1" = "" ]; then + usage +fi + +while getopts "b:m:h:d:" options; do + case $options in + b ) BASEDIR=$OPTARG;; + m ) MANIFESTS=$OPTARG;; + d ) DEBUG=$OPTARG;; + h ) usage;; + \? ) usage;; + * ) usage;; + esac +done + +CWD=`pwd` + +# Configure with "puppet apply" +if [ "$DEBUG" == "true" ]; then + PUPPET_APPLY="/opt/puppetlabs/bin/puppet apply --verbose --debug --trace --summarize" +else + PUPPET_APPLY="/opt/puppetlabs/bin/puppet apply" +fi + +# Pass variables to Puppet manifests via environment variables +export FACTER_profile='/etc/profile.d/myprofile.sh' +export FACTER_basedir="$BASEDIR" +export FACTER_keycloak_version='12.0.2' +export FACTER_keycloak_datasource_host='db.local' +export FACTER_keycloak_datasource_dbname='keycloak' +export FACTER_keycloak_datasource_username='keycloak' +export FACTER_keycloak_datasource_password='keycloak' +export FACTER_keycloak_admin_user='admin' +export FACTER_keycloak_admin_user_password='changeme' +export FACTER_keycloak_wildfly_user='wildfly' +export FACTER_keycloak_wildfly_user_password='wildfly' +export FACTER_manage_package_repo='false' +export FACTER_postgresql_version='9.6' +export FACTER_postgresql_manage_package_repo='true' +export FACTER_postgresql_listen_address='*' +export FACTER_db_username='keycloak' +export FACTER_db_password='keycloak' +export FACTER_db_database='keycloak' +export FACTER_db_connection_limit='300' + +for manifest in $MANIFESTS; do + $PUPPET_APPLY /vagrant/vagrant/$manifest +done + +cd $CWD diff --git a/vagrant/slave.pp b/vagrant/slave.pp new file mode 100644 index 0000000..0f876bc --- /dev/null +++ b/vagrant/slave.pp @@ -0,0 +1,25 @@ +notify { 'Installing Slave': } + +class { '::keycloak': + operating_mode => 'domain', + role => 'slave', + enable_jdbc_ping => true, + management_bind_address => '192.168.168.252', + wildfly_user => $keycloak_wildfly_user, + wildfly_user_password => $keycloak_wildfly_user_password, + master_address => '192.168.168.253', + manage_install => true, + manage_datasource => false, + version => $keycloak_version, + datasource_driver => 'postgresql', + datasource_host => $keycloak_datasource_host, + datasource_port => 5432, + datasource_dbname => $keycloak_datasource_dbname, + datasource_username => $keycloak_datasource_username, + datasource_password => $keycloak_datasource_password, + admin_user => $keycloak_admin_user, + admin_user_password => $keycloak_admin_user_password, + service_bind_address => '0.0.0.0', + proxy_https => false, +} +