diff --git a/actions/lib.sh b/actions/lib.sh index b3b87691e6a32eaad1ee0b44eb739a9e18cae4d1..4d842ba7a6a5cf8d1df973cb48c1cc8bc34f2506 100644 --- a/actions/lib.sh +++ b/actions/lib.sh @@ -165,7 +165,7 @@ function log() { shift fmt="$1" shift - printf "%s%s:%s $fmt\n" "$(ccode "$colorcode")" "$level" "$(ccode '\x1b[0m')" "$@" + 1>&2 printf "%s%s:%s $fmt\n" "$(ccode "$colorcode")" "$level" "$(ccode '\x1b[0m')" "$@" } function errorf() { diff --git a/actions/release-migrations/v12-10-require-vault-policies-update.sh b/actions/release-migrations/v12-10-require-vault-policies-update.sh new file mode 100755 index 0000000000000000000000000000000000000000..b938c65c9372f26e166140494d034745beb894a1 --- /dev/null +++ b/actions/release-migrations/v12-10-require-vault-policies-update.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash + +set -euo pipefail +actions_dir="$(dirname "$0")/.." + +# shellcheck source=actions/lib.sh +. "$actions_dir/lib.sh" + +function get_clustername() { + yq --raw-output '.vault_cluster_name // error("unset")' "${group_vars_dir}/all/vault-backend.yaml" +} + +# Check if Vault policies are up-to-date +# so that the orchestrator is able to create, read, delete and update the ch-k8s-lbaas-shared-secret + +vault_token_kind="$( \ + { vault token lookup -non-interactive -format=json 2>/dev/null || true; } \ + | jq --raw-output ' + if type == "object" then ( + .data.policies + | if index("root") != null then "root" + elif index("yaook/orchestrator") != null then "orchestrator" + else "unknown" end + ) end + ' \ +)" +orchestrator_has_correct_cap="$( \ + case "${vault_token_kind}" in + root|orchestrator) + if [ "${vault_token_kind}" == "root" ]; then + notef "Got a Vault token with root policy" + vault_orchestrator_token="$(vault token create -policy=yaook/orchestrator -field=token)" + else + notef "Got a Vault token with yaook/orchestrator policy" + vault_orchestrator_token="${VAULT_TOKEN:?}" + fi + VAULT_TOKEN="${vault_orchestrator_token:?}" \ + vault token capabilities -format=json yaook/"$(get_clustername)"/kv/data/ch-k8s-lbaas-shared-secret + ;; + unknown) + notef "Got a Vault token without root or yaook/orchestrator policy" + echo '[]' + ;; + *) + notef "Got NO Vault token" + echo '[]' + ;; + esac | jq ' + index("read") != null + and index("update") != null + and index("create") != null + and index("delete") != null + ' \ +)" + +if $orchestrator_has_correct_cap; then + notef "Vault policies look up-to-date" +else + case "${vault_token_kind}" in + root) + notef "Updating Vault policies" + run "$actions_dir/../tools/vault/init.sh" + ;; + orchestrator) + errorf "Vault policies are not up-to-date" + # shellcheck disable=SC2016 + notef 'Please run `VAULT_TOKEN=${vault_root_token:?} ./managed-k8s/tools/vault/init.sh`' + exit 1 + ;; + *) + warningf 'Cannot verify that Vault policies are up-to-date' + # shellcheck disable=SC2016 + warningf 'Please ensure `VAULT_TOKEN=${vault_root_token:?} ./managed-k8s/tools/vault/init.sh` has been executed on this release.' + ;; + esac +fi diff --git a/ci/config/default.nix b/ci/config/default.nix index ac0bf775e3b93644e0642e64d60eacbaeca7a8d0..0e2ba21b85af2b3aed18c0222de60591aba371d7 100644 --- a/ci/config/default.nix +++ b/ci/config/default.nix @@ -167,7 +167,6 @@ in { }; ch-k8s-lbaas = { enabled = true; - shared_secret = "IYeOlEFO1h3uc9x1bdw9thNNgmn1gm8dmzos3f04PLmFjt3d"; agent_port = 15203; }; kubernetes = { diff --git a/docs/_releasenotes/2123.BREAKING.1.ch-k8s-lbaas-secret-vault b/docs/_releasenotes/2123.BREAKING.1.ch-k8s-lbaas-secret-vault new file mode 100644 index 0000000000000000000000000000000000000000..cc863cd0946f134e96ae6d434a80d277287cc44c --- /dev/null +++ b/docs/_releasenotes/2123.BREAKING.1.ch-k8s-lbaas-secret-vault @@ -0,0 +1,8 @@ +Vault policies must be updated for existing Vault instances which serve as backend for clusters. +A Vault root token is required to do so. + +.. code:: + + VAULT_TOKEN=$vault_root_token ./managed-k8s/tools/vault/init.sh + +. diff --git a/docs/_releasenotes/2123.change.1.ch-k8s-lbaas-uninstall b/docs/_releasenotes/2123.change.1.ch-k8s-lbaas-uninstall new file mode 100644 index 0000000000000000000000000000000000000000..5ea2dd06a05a6e8783d98af5952c63ac90018db3 --- /dev/null +++ b/docs/_releasenotes/2123.change.1.ch-k8s-lbaas-uninstall @@ -0,0 +1 @@ +It is now ensured that all components of ch-k8s-lbaas are deconfigured and absent if the option :ref:`configuration-options.yk8s.ch-k8s-lbaas.enabled` is ``false``. diff --git a/docs/_releasenotes/2123.feature.1.ch-k8s-lbaas-secret-vault b/docs/_releasenotes/2123.feature.1.ch-k8s-lbaas-secret-vault new file mode 100644 index 0000000000000000000000000000000000000000..57c77dc70f1bffe7208e2176899cbd3f6071b89c --- /dev/null +++ b/docs/_releasenotes/2123.feature.1.ch-k8s-lbaas-secret-vault @@ -0,0 +1,2 @@ +The shared secret for :doc:`/user/explanation/services/ch-k8s-lbaas` is now auto-generated and handled via Vault. +Previously, the user was expected to manually generate and configure it in :ref:`configuration-options.yk8s.ch-k8s-lbaas.shared_secret`. diff --git a/docs/_releasenotes/2123.removal.1.ch-k8s-lbaas-secret-vault b/docs/_releasenotes/2123.removal.1.ch-k8s-lbaas-secret-vault new file mode 100644 index 0000000000000000000000000000000000000000..53d10fb2ba48355e3d59303759cb5a1c1b8d6ff1 --- /dev/null +++ b/docs/_releasenotes/2123.removal.1.ch-k8s-lbaas-secret-vault @@ -0,0 +1,3 @@ +The option :ref:`configuration-options.yk8s.ch-k8s-lbaas.shared_secret` has been marked as deprecated. +The secret is handled via Vault from now on and if the option is set, the option's value is automatically moved to Vault on a rollout. +Once a rollout has been done, the option should be unset as it is going to be removed in a future release. diff --git a/docs/user/reference/options/yk8s.ch-k8s-lbaas.rst b/docs/user/reference/options/yk8s.ch-k8s-lbaas.rst index 5a9096c862e21f3706a250f6d1bb9428c7d56948..b88d6d2892dc3e9efca42e6e1708a0c0d01f1013 100644 --- a/docs/user/reference/options/yk8s.ch-k8s-lbaas.rst +++ b/docs/user/reference/options/yk8s.ch-k8s-lbaas.rst @@ -4,6 +4,9 @@ yk8s.ch-k8s-lbaas ^^^^^^^^^^^^^^^^^ +``ch-k8s-lbaas`` is a LoadBalancing-solution for clusters running on OpenStack +as well as clusters running on bare metal. +Further information about it can be found here: :doc:`/user/explanation/services/ch-k8s-lbaas`. .. _configuration-options.yk8s.ch-k8s-lbaas.agent_port: @@ -360,6 +363,11 @@ https://gitlab.com/alasca.cloud/tarook/tarook/-/tree/devel/nix/yk8s/k8s-suppleme ``yk8s.ch-k8s-lbaas.shared_secret`` ################################### +.. attention:: DEPRECATED + + This option is going to be removed soon + since the shared secret is now stored in and automatically handled via Vault. + A unique, random, base64-encoded secret. To generate such a secret, you can use the following command: $ dd if=/dev/urandom bs=16 count=1 status=none | base64 @@ -367,7 +375,12 @@ $ dd if=/dev/urandom bs=16 count=1 status=none | base64 **Type:**:: - Base64 encoded string + null or Base64 encoded string + + +**Default:**:: + + null **Example:**:: diff --git a/k8s-supplements/ansible/install-ch-k8s-lbaas.yaml b/k8s-supplements/ansible/install-ch-k8s-lbaas.yaml index f6a488421eb64095c6ab22360d22a9d61395d364..73d1b9c6bc876e7dc9d0e79d3538e65c99eab88d 100644 --- a/k8s-supplements/ansible/install-ch-k8s-lbaas.yaml +++ b/k8s-supplements/ansible/install-ch-k8s-lbaas.yaml @@ -28,9 +28,9 @@ vars_files: - vars/disruption.yaml - vars/retries.yaml + - vars/vault-config.yaml roles: - role: ch-k8s-lbaas-controller - when: ch_k8s_lbaas_enabled tags: - ch-k8s-lbaas - ch-k8s-lbaas-controller @@ -41,9 +41,9 @@ - vars/auto_generated_preamble.yaml - vars/disruption.yaml - vars/retries.yaml + - vars/vault-config.yaml roles: - role: ch-k8s-lbaas-agent - when: ch_k8s_lbaas_enabled tags: - ch-k8s-lbaas - ch-k8s-lbaas-agent diff --git a/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/defaults/main.yaml b/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/defaults/main.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a47b3d5032a804c7d95a098f6ec5f5c207eef7ce --- /dev/null +++ b/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/defaults/main.yaml @@ -0,0 +1,5 @@ +--- +ch_k8s_lbaas_agent_state_directory: "/var/lib/ch-k8s-lbaas-agent" +ch_k8s_lbaas_agent_config_directory: "/etc/ch-k8s-lbaas-agent" +ch_k8s_lbaas_agent_binary_path: "/usr/local/bin/ch-k8s-lbaas-agent" +... diff --git a/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/meta/main.yaml b/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/meta/main.yaml index 2f410663450af98902439f99538902bbd0686fbb..eae97beea96bff3025f15a8629784ee1fdf8e5dc 100644 --- a/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/meta/main.yaml +++ b/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/meta/main.yaml @@ -5,4 +5,5 @@ galaxy_info: dependencies: - config/k8s-config - ch-k8s-lbaas-common +- helpers/vault-approle ... diff --git a/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/tasks/install.yaml b/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/tasks/install.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a5221e4f7225cdaa6a4bec3bb6e5eb4c0d5be93b --- /dev/null +++ b/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/tasks/install.yaml @@ -0,0 +1,114 @@ +--- +- name: Import dependency roles needed when running on OpenStack + when: openstack_enabled + block: + - name: Import bird + import_role: + name: bird + - name: Import nftables + import_role: + name: nftables + +- name: Deploy LBaaS agent + become: true + block: + - name: Create user + user: + system: true + name: "{{ ch_k8s_lbaas_agent_user }}" + home: /dev/null + shell: /bin/false + + - name: Create configuration directory + file: + state: directory + owner: root + group: "{{ ch_k8s_lbaas_agent_user }}" + mode: u=rwx,g=rx,o-rwx + path: "{{ ch_k8s_lbaas_agent_config_directory }}" + + # This is used to avoid having to re-download the binary from the source + # all the time. This task will be "changed" whenever the URL or checksum + # passed to the get_url module below changes, allowing to force a + # re-download exactly when necessary. + - name: Store id tag + template: + src: binary_id_tag.j2 + dest: "{{ ch_k8s_lbaas_agent_config_directory }}/version" + owner: root + group: root + mode: ugo-rwx + register: version + + - name: Download executable + get_url: + url: "{{ ch_k8s_lbaas_agent_binary_url }}" + checksum: "{{ ch_k8s_lbaas_agent_binary_checksum }}" + dest: "{{ ch_k8s_lbaas_agent_binary_path }}" + force: "{{ (version is changed) | ternary(True, False) }}" + owner: root + group: root + mode: ugo=rx + register: task_result + until: task_result is not failed + retries: "{{ error_retries }}" + delay: "{{ error_delay }}" + notify: + - restart ch-k8s-lbaas-agent + + - name: Configure service + template: + src: agent-config.toml.j2 + dest: "{{ ch_k8s_lbaas_agent_config_directory }}/config.toml" + owner: root + group: "{{ ch_k8s_lbaas_agent_user }}" + mode: u=rw,g=r,o-rwx + notify: + - restart ch-k8s-lbaas-agent + + - name: Configure sudoers + template: + src: sudoers.j2 + dest: /etc/sudoers.d/lbaas + owner: root + group: root + mode: u=rw,go=r + validate: /usr/sbin/visudo -cf %s + + - name: Define service + template: + src: ch-k8s-lbaas-agent.service.j2 + dest: /etc/systemd/system/ch-k8s-lbaas-agent.service + owner: root + group: root + mode: u=rw,go=r + register: agent_service + notify: + - restart ch-k8s-lbaas-agent + + # Migration task, remove me with TAROOK v12 + - name: Remove legacy no-SNAT configuration + file: + state: absent + path: "/etc/bird.d/01-no-snat.conf" + notify: restart bird + + - name: Start service + systemd: + name: ch-k8s-lbaas-agent + daemon_reload: "{{ agent_service is changed }}" + state: started + enabled: true + + # Need to do that *after* the service is started, because systemd creates the + # /var/lib/ch-k8s-lbaas-agent/nftables directory. + - name: Configure firewall for agent access + template: + src: nftables.conf.j2 + dest: "{{ ch_k8s_lbaas_agent_state_directory }}/nftables/access.conf" + owner: root + group: root + mode: "u=rw,go=r" + notify: reload nftables + when: openstack_enabled +... diff --git a/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/tasks/main.yaml b/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/tasks/main.yaml index 83b3c72c8155f8a437abc91f935bc91f947dd5f3..921a376713b9e32a6502a655bd1d0fbd7a6551db 100644 --- a/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/tasks/main.yaml +++ b/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/tasks/main.yaml @@ -1,141 +1,9 @@ --- -- name: Import dependency roles needed when running on OpenStack - when: openstack_enabled - block: - - name: Import bird - import_role: - name: bird - - name: Import nftables - import_role: - name: nftables +- name: Install ch-k8s-lbaas-agent + when: ch_k8s_lbaas_enabled + include_tasks: install.yaml -- name: Deploy LBaaS agent - become: true - block: - - name: Create user - user: - system: true - name: "{{ ch_k8s_lbaas_agent_user }}" - home: /dev/null - shell: /bin/false - - - name: Create configuration directory - file: - state: directory - owner: root - group: "{{ ch_k8s_lbaas_agent_user }}" - mode: u=rwx,g=rx,o-rwx - path: /etc/ch-k8s-lbaas-agent - - # This is used to avoid having to re-download the binary from the source - # all the time. This task will be "changed" whenever the URL or checksum - # passed to the get_url module below changes, allowing to force a - # re-download exactly when necessary. - - name: Store id tag - template: - src: binary_id_tag.j2 - dest: /etc/ch-k8s-lbaas-agent/version - owner: root - group: root - mode: ugo-rwx - register: version - - - name: Download executable - get_url: - url: "{{ ch_k8s_lbaas_agent_binary_url }}" - checksum: "{{ ch_k8s_lbaas_agent_binary_checksum }}" - dest: /usr/local/bin/ch-k8s-lbaas-agent - force: "{{ (version is changed) | ternary(True, False) }}" - owner: root - group: root - mode: ugo=rx - register: task_result - until: task_result is not failed - retries: "{{ error_retries }}" - delay: "{{ error_delay }}" - notify: - - restart ch-k8s-lbaas-agent - - - name: Configure service - template: - src: agent-config.toml.j2 - dest: /etc/ch-k8s-lbaas-agent/config.toml - owner: root - group: "{{ ch_k8s_lbaas_agent_user }}" - mode: u=rw,g=r,o-rwx - notify: - - restart ch-k8s-lbaas-agent - - - name: Configure sudoers - template: - src: sudoers.j2 - dest: /etc/sudoers.d/lbaas - owner: root - group: root - mode: u=rw,go=r - validate: /usr/sbin/visudo -cf %s - - - name: Define service - template: - src: ch-k8s-lbaas-agent.service.j2 - dest: /etc/systemd/system/ch-k8s-lbaas-agent.service - owner: root - group: root - mode: u=rw,go=r - register: agent_service - notify: - - restart ch-k8s-lbaas-agent - - # Migration task, remove me with TAROOK v12 - - name: Remove legacy no-SNAT configuration - file: - state: absent - path: "/etc/bird.d/01-no-snat.conf" - notify: restart bird - - - name: Start service - systemd: - name: ch-k8s-lbaas-agent - daemon_reload: "{{ agent_service is changed }}" - state: started - enabled: true - - # Need to do that *after* the service is started, because systemd creates the - # /var/lib/ch-k8s-lbaas-agent/nftables directory. - - name: Configure firewall for agent access - template: - src: nftables.conf.j2 - dest: /var/lib/ch-k8s-lbaas-agent/nftables/access.conf - owner: root - group: root - mode: "u=rw,go=r" - notify: reload nftables - when: openstack_enabled - -- name: Disable LBaaS agent +- name: Uninstall ch-k8s-lbaas-agent when: not ch_k8s_lbaas_enabled - block: - - name: Detect LBaaS agent # noqa command-instead-of-module - command: - args: - argv: - - systemctl - - is-active - - ch-k8s-lbaas-agent - register: agent_detect - failed_when: false - changed_when: "{{ agent_detect.rc == 0 }}" - - - name: Disable LBaaS agent # noqa no-handler - service: - name: ch-k8s-lbaas-agent - state: stopped - enabled: false - when: agent_detect is changed - - - name: Remove firewall hole for agent access - file: - state: absent - path: /var/lib/ch-k8s-lbaas-agent/nftables/access.conf - notify: reload nftables + include_tasks: uninstall.yaml ... diff --git a/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/tasks/uninstall.yaml b/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/tasks/uninstall.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f79aa0e10da92954ea1364578f11e2b5c64d4af2 --- /dev/null +++ b/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/tasks/uninstall.yaml @@ -0,0 +1,64 @@ +--- +- name: Remove LBaaS agent + become: true + block: + - name: Deconfigure firewall for agent access + file: + state: absent + path: "{{ ch_k8s_lbaas_agent_state_directory }}/nftables/access.conf" + notify: reload nftables + when: openstack_enabled + + - name: Stop and disable service + block: + - name: Get service facts + ansible.builtin.service_facts: + + - name: Stop and disable service + when: ansible_facts['services']['ch-k8s-lbaas-agent.service'] is defined + systemd: + name: ch-k8s-lbaas-agent + state: stopped + enabled: false + + - name: Delete service definition + file: + state: absent + path: /etc/systemd/system/ch-k8s-lbaas-agent.service + + - name: Deconfigure sudoers + file: + state: absent + path: /etc/sudoers.d/lbaas + + - name: Delete configuration directory + file: + state: absent + path: "{{ ch_k8s_lbaas_agent_config_directory }}" + + - name: Delete executable + file: + state: absent + path: "{{ ch_k8s_lbaas_agent_binary_path }}" + + - name: Delete agent directory + file: + state: absent + path: "{{ ch_k8s_lbaas_agent_state_directory }}" + + - name: Delete user + user: + name: "{{ ch_k8s_lbaas_agent_user }}" + state: absent + remove: false # as we set home to /dev/null + +- name: Import dependency roles needed when running on OpenStack + when: openstack_enabled + block: + - name: Import bird + import_role: + name: bird + - name: Import nftables + import_role: + name: nftables +... diff --git a/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/templates/agent-config.toml.j2 b/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/templates/agent-config.toml.j2 index 4f373bda901902debf96bb1a6751ff55ce32c56c..614a71c33ffa8551213f4bd43db9bcc27acf17f3 100644 --- a/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/templates/agent-config.toml.j2 +++ b/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/templates/agent-config.toml.j2 @@ -1,6 +1,16 @@ {{ _auto_generated_preamble }} -shared-secret={{ ch_k8s_lbaas_shared_secret | to_json }} +shared-secret={{ + lookup( + 'community.hashi_vault.vault_kv2_get', + ch_k8s_lbaas_shared_secret_vault_path, + engine_mount_point=ch_k8s_lbaas_shared_secret_vault_mount_point, + mount_point=vault_nodes_approle, + auth_method='approle', + role_id=vault_node_role_id, + secret_id=vault_node_secret_id + ).data.data.shared_secret | to_json +}} bind-address="0.0.0.0" bind-port={{ ch_k8s_lbaas_agent_port | to_json }} @@ -10,11 +20,11 @@ priority={{ vrrp_priorities[(groups['frontend'] | sort).index(inventory_hostname virtual-router-id-base=10 [keepalived.service] -config-file="/var/lib/ch-k8s-lbaas-agent/keepalived/lbaas.conf" +config-file={{ ch_k8s_lbaas_agent_state_directory + '/keepalived/lbaas.conf' | quote }} check-delay=2 [nftables] enable-snat={{ ch_k8s_lbaas_enable_snat | bool | lower }} [nftables.service] -config-file="/var/lib/ch-k8s-lbaas-agent/nftables/lbaas.conf" +config-file={{ ch_k8s_lbaas_agent_state_directory + '/nftables/lbaas.conf' | quote }} diff --git a/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/templates/ch-k8s-lbaas-agent.service.j2 b/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/templates/ch-k8s-lbaas-agent.service.j2 index 5a25554c122012ce07573651d4aea1b634434256..93e5b8fef8a1cd3f7be5175a63b9b1d791b82e80 100644 --- a/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/templates/ch-k8s-lbaas-agent.service.j2 +++ b/k8s-supplements/ansible/roles/ch-k8s-lbaas-agent/templates/ch-k8s-lbaas-agent.service.j2 @@ -8,7 +8,7 @@ WantedBy=multi-user.target [Service] Type=simple -ExecStart=/usr/local/bin/ch-k8s-lbaas-agent --config /etc/ch-k8s-lbaas-agent/config.toml +ExecStart={{ ch_k8s_lbaas_agent_binary_path | quote }} --config {{ ch_k8s_lbaas_agent_config_directory | quote }}/config.toml User={{ ch_k8s_lbaas_agent_user }} Group={{ ch_k8s_lbaas_agent_user }} diff --git a/k8s-supplements/ansible/roles/ch-k8s-lbaas-common/defaults/main.yaml b/k8s-supplements/ansible/roles/ch-k8s-lbaas-common/defaults/main.yaml index 6293e381d861dd2857a0e55f66d82bb14f929145..6edf75424a67969f7531cca580e189707f6f3598 100644 --- a/k8s-supplements/ansible/roles/ch-k8s-lbaas-common/defaults/main.yaml +++ b/k8s-supplements/ansible/roles/ch-k8s-lbaas-common/defaults/main.yaml @@ -18,4 +18,6 @@ ch_k8s_lbaas_agent_binary_checksums: "0.8.0": "sha256:339605b3238066ad074522e893022ba94f09fc6b0a1623fafd1ad0f9c66e9a69" "0.9.0": "sha256:a9b6171078d41f415b11cf600700942ca714e2236fcac31460733fcb9a816cf9" ch_k8s_lbaas_agent_binary_checksum: "{{ ch_k8s_lbaas_agent_binary_checksums[ch_k8s_lbaas_version] | default('invalid checksum') }}" +ch_k8s_lbaas_shared_secret_vault_mount_point: "{{ vault_path_prefix }}/{{ vault_cluster_name }}/kv" +ch_k8s_lbaas_shared_secret_vault_path: "ch-k8s-lbaas-shared-secret" ... diff --git a/k8s-supplements/ansible/roles/ch-k8s-lbaas-common/tasks/main.yaml b/k8s-supplements/ansible/roles/ch-k8s-lbaas-common/tasks/main.yaml index f895d9b1ec2c4632c6b3b4e167e49fce99776355..ce985f1887232c58c4698e6616550af355b3739f 100644 --- a/k8s-supplements/ansible/roles/ch-k8s-lbaas-common/tasks/main.yaml +++ b/k8s-supplements/ansible/roles/ch-k8s-lbaas-common/tasks/main.yaml @@ -1,15 +1,111 @@ --- -- name: Validate the shared secret - # Split validation into two tasks to improve error messaging +- name: Generate a shared secret if not configured when: ch_k8s_lbaas_enabled + become: false + delegate_to: "{{ groups['orchestrator'] | first }}" + run_once: true block: - - name: Attempt to decode the shared secret # noqa ignore-errors - set_fact: - _: "{{ ch_k8s_lbaas_shared_secret | b64decode }}" - ignore_errors: true - - - name: Fail if the shared secret couldn't be decoded - when: _ | default('') | length < 1 - fail: - msg: "[ch-k8s-lbaas] Ensure that the `shared_secret` is base64 encoded." + - name: Manage shared secret in Vault + environment: + ANSIBLE_HASHI_VAULT_URL: "{{ lookup('env', 'VAULT_ADDR') }}" + ANSIBLE_HASHI_VAULT_CA_CERT: "{{ lookup('env', 'VAULT_CACERT') }}" + block: + - name: Lookup shared secret in Vault # noqa ignore-errors + no_log: true # prevent secret from being exposed + ignore_errors: true + community.hashi_vault.vault_kv2_get: + path: "{{ ch_k8s_lbaas_shared_secret_vault_path }}" + engine_mount_point: "{{ ch_k8s_lbaas_shared_secret_vault_mount_point }}" + auth_method: "{{ vault_caller_auth_method }}" + mount_point: "{{ vault_caller_auth_mount_point }}" + token: "{{ vault_caller_token }}" + role_id: "{{ vault_caller_role_id }}" + secret_id: "{{ vault_caller_secret_id }}" + token_validate: false + register: shared_secret_lookup + + # In a future release, when dropping the shared secret option from + # the configuration, the when-condition should be changed to + # when: shared_secret_lookup is failed + - name: Generate shared secret if none exists, yet + when: shared_secret_lookup is failed and ch_k8s_lbaas_shared_secret is none + set_fact: + ch_k8s_lbaas_shared_secret_gen: "{{ lookup('pipe', 'dd if=/dev/urandom bs=30 count=1 status=none | base64') }}" + + - name: Store shared secret in Vault + block: + # In a future release, when dropping the shared_secret option from + # the configuration, we should always write + # "ch_k8s_lbaas_shared_secret_gen" into Vault + # and we should remove the block-rescue workaround to print a proper error message + - name: Store shared secret in Vault + no_log: false # prevent secret from being exposed + community.hashi_vault.vault_kv2_write: + path: "{{ ch_k8s_lbaas_shared_secret_vault_path }}" + engine_mount_point: "{{ ch_k8s_lbaas_shared_secret_vault_mount_point }}" + auth_method: "{{ vault_caller_auth_method }}" + mount_point: "{{ vault_caller_auth_mount_point }}" + token: "{{ vault_caller_token }}" + role_id: "{{ vault_caller_role_id }}" + secret_id: "{{ vault_caller_secret_id }}" + token_validate: false + read_before_write: true + data: + # use the value configured via Nix until the option gets removed + # fallback to a generated secret if shared secret is not configured via Nix + shared_secret: "{{ ch_k8s_lbaas_shared_secret is not none | ternary(ch_k8s_lbaas_shared_secret, shared_secret_lookup.data.data.shared_secret | default(ch_k8s_lbaas_shared_secret_gen)) }}" + rescue: + - name: Fail if shared secret could not be written into Vault + fail: + msg: | + Failed to write the shared secret into Vault at + '{{ ch_k8s_lbaas_shared_secret_vault_mount_point }}/data/{{ ch_k8s_lbaas_shared_secret_vault_path }}'. + + Does your {{ vault_caller_auth_method }}'s Vault policy allow it? + + You might need to complete the necessary steps + outlined in the changelog at + https://docs.tarook.cloud/release/v12.0/releasenotes.html + +- name: Delete shared secret from Vault + when: not ch_k8s_lbaas_enabled + become: false + delegate_to: "{{ groups['orchestrator'] | first }}" + run_once: true + block: + # NOTE: The lookup ahead is not technically needed, but makes output more + # clear as the "community.hashi_vault.vault_kv2_delete" always marks + # the task as changed even if the kv object already was deleted. + - name: Delete shared secret from Vault + environment: + ANSIBLE_HASHI_VAULT_URL: "{{ lookup('env', 'VAULT_ADDR') }}" + ANSIBLE_HASHI_VAULT_CA_CERT: "{{ lookup('env', 'VAULT_CACERT') }}" + block: + - name: Lookup shared secret in Vault # noqa ignore-errors + no_log: true # prevent secret from being exposed + ignore_errors: true + community.hashi_vault.vault_kv2_get: + path: "{{ ch_k8s_lbaas_shared_secret_vault_path }}" + engine_mount_point: "{{ ch_k8s_lbaas_shared_secret_vault_mount_point }}" + auth_method: "{{ vault_caller_auth_method }}" + mount_point: "{{ vault_caller_auth_mount_point }}" + token: "{{ vault_caller_token }}" + role_id: "{{ vault_caller_role_id }}" + secret_id: "{{ vault_caller_secret_id }}" + token_validate: false + register: shared_secret_lookup + + # NOTE: We don't require explicit consent here as the deletion is not + # actually destructive. Users are able to undelete. + - name: Delete shared secret from Vault # noqa ignore-errors + when: shared_secret_lookup is not failed + community.hashi_vault.vault_kv2_delete: + path: "{{ ch_k8s_lbaas_shared_secret_vault_path }}" + engine_mount_point: "{{ ch_k8s_lbaas_shared_secret_vault_mount_point }}" + auth_method: "{{ vault_caller_auth_method }}" + mount_point: "{{ vault_caller_auth_mount_point }}" + token: "{{ vault_caller_token }}" + role_id: "{{ vault_caller_role_id }}" + secret_id: "{{ vault_caller_secret_id }}" + token_validate: false ... diff --git a/k8s-supplements/ansible/roles/ch-k8s-lbaas-controller/tasks/install.yaml b/k8s-supplements/ansible/roles/ch-k8s-lbaas-controller/tasks/install.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b9256b93b3d03307d08721d1998a912ac90e858f --- /dev/null +++ b/k8s-supplements/ansible/roles/ch-k8s-lbaas-controller/tasks/install.yaml @@ -0,0 +1,42 @@ +--- +- name: Deploy LBaaS controller + block: + - name: Configure LBaaS controller + kubernetes.core.k8s: + apply: true + definition: + apiVersion: v1 + kind: Secret + type: Opaque + data: + controller-config.toml: "{{ lookup('template', 'controller-config.toml.j2') | b64encode }}" + metadata: + name: ch-k8s-lbaas-controller-config + namespace: kube-system + validate: + fail_on_error: true + strict: true + notify: + - restart ch-k8s-lbaas-controller + # Retry this task on failures + register: k8s_apply + until: k8s_apply is not failed + retries: "{{ error_retries }}" + delay: "{{ error_delay }}" + + - name: Deploy LBaaS controller + kubernetes.core.k8s: + apply: true + definition: "{{ lookup('template', item) }}" + validate: + fail_on_error: true + strict: true + with_items: + - controller-rbac.yaml.j2 + - controller.yaml.j2 + # Retry this task on failures + register: k8s_apply + until: k8s_apply is not failed + retries: "{{ error_retries }}" + delay: "{{ error_delay }}" +... diff --git a/k8s-supplements/ansible/roles/ch-k8s-lbaas-controller/tasks/main.yaml b/k8s-supplements/ansible/roles/ch-k8s-lbaas-controller/tasks/main.yaml index b9256b93b3d03307d08721d1998a912ac90e858f..c1d6fac75e8e79588a347429fb8fe4255dba5933 100644 --- a/k8s-supplements/ansible/roles/ch-k8s-lbaas-controller/tasks/main.yaml +++ b/k8s-supplements/ansible/roles/ch-k8s-lbaas-controller/tasks/main.yaml @@ -1,42 +1,9 @@ --- -- name: Deploy LBaaS controller - block: - - name: Configure LBaaS controller - kubernetes.core.k8s: - apply: true - definition: - apiVersion: v1 - kind: Secret - type: Opaque - data: - controller-config.toml: "{{ lookup('template', 'controller-config.toml.j2') | b64encode }}" - metadata: - name: ch-k8s-lbaas-controller-config - namespace: kube-system - validate: - fail_on_error: true - strict: true - notify: - - restart ch-k8s-lbaas-controller - # Retry this task on failures - register: k8s_apply - until: k8s_apply is not failed - retries: "{{ error_retries }}" - delay: "{{ error_delay }}" +- name: Install ch-k8s-lbaas-controller + when: ch_k8s_lbaas_enabled + include_tasks: install.yaml - - name: Deploy LBaaS controller - kubernetes.core.k8s: - apply: true - definition: "{{ lookup('template', item) }}" - validate: - fail_on_error: true - strict: true - with_items: - - controller-rbac.yaml.j2 - - controller.yaml.j2 - # Retry this task on failures - register: k8s_apply - until: k8s_apply is not failed - retries: "{{ error_retries }}" - delay: "{{ error_delay }}" +- name: Uninstall ch-k8s-lbaas-controller + when: not ch_k8s_lbaas_enabled + include_tasks: uninstall.yaml ... diff --git a/k8s-supplements/ansible/roles/ch-k8s-lbaas-controller/tasks/uninstall.yaml b/k8s-supplements/ansible/roles/ch-k8s-lbaas-controller/tasks/uninstall.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8118ab5e54b887a2c643b1fc2f1196ff975128af --- /dev/null +++ b/k8s-supplements/ansible/roles/ch-k8s-lbaas-controller/tasks/uninstall.yaml @@ -0,0 +1,32 @@ +--- +- name: Remove LBaaS controller + block: + - name: Remove LBaaS controller + kubernetes.core.k8s: + state: absent + definition: "{{ lookup('template', item) }}" + validate: + fail_on_error: true + strict: true + with_items: + - controller-rbac.yaml.j2 + - controller.yaml.j2 + # Retry this task on failures + register: k8s_apply + until: k8s_apply is not failed + retries: "{{ error_retries }}" + delay: "{{ error_delay }}" + + - name: Deconfigure LBaaS controller + kubernetes.core.k8s: + state: absent + api_version: v1 + kind: Secret + namespace: kube-system + name: ch-k8s-lbaas-controller-config + # Retry this task on failures + register: k8s_apply + until: k8s_apply is not failed + retries: "{{ error_retries }}" + delay: "{{ error_delay }}" +... diff --git a/k8s-supplements/ansible/roles/ch-k8s-lbaas-controller/templates/controller-config.toml.j2 b/k8s-supplements/ansible/roles/ch-k8s-lbaas-controller/templates/controller-config.toml.j2 index b0bbf2da0e7ffeb54dc253a0490ee793e3299f0d..f785dd5e6ebd8fafa6b9b37917c84f3aa65cc838 100644 --- a/k8s-supplements/ansible/roles/ch-k8s-lbaas-controller/templates/controller-config.toml.j2 +++ b/k8s-supplements/ansible/roles/ch-k8s-lbaas-controller/templates/controller-config.toml.j2 @@ -33,7 +33,18 @@ ipv4-addresses = [ [agents] additional-address-pairs=[{% if ipv4_enabled %}{{ networking_fixed_ip | to_json }},"0.0.0.0/0",{% endif %}{% if ipv6_enabled %}{{ networking_fixed_ip_v6 | to_json }},"::/0"{% endif %}] -shared-secret={{ ch_k8s_lbaas_shared_secret | to_json }} +shared-secret={{ + lookup( + 'community.hashi_vault.vault_kv2_get', + ch_k8s_lbaas_shared_secret_vault_path, + engine_mount_point=ch_k8s_lbaas_shared_secret_vault_mount_point, + auth_method=vault_caller_auth_method, + mount_point=vault_caller_auth_mount_point, + token=vault_caller_token, + role_id=vault_caller_role_id, + secret_id=vault_caller_secret_id + ).data.data.shared_secret | to_json +}} token-lifetime=60 {% if ch_k8s_lbaas_port_manager == "openstack" %} diff --git a/k8s-supplements/ansible/roles/monitoring_v2/tasks/install_monitors.yaml b/k8s-supplements/ansible/roles/monitoring_v2/tasks/install_monitors.yaml index c36c669fb339be6daea9bdb22a3f5ba1d52ace3f..c8c49c95de79cce50c1d69ae9509a95aac192739 100644 --- a/k8s-supplements/ansible/roles/monitoring_v2/tasks/install_monitors.yaml +++ b/k8s-supplements/ansible/roles/monitoring_v2/tasks/install_monitors.yaml @@ -44,14 +44,14 @@ retries: "{{ error_retries }}" delay: "{{ error_delay }}" - - name: Create ch-k8s-LBaaS service monitors (frontend) + - name: "{{ ch_k8s_lbaas_enabled | ternary('Create', 'Delete') }} ch-k8s-LBaaS service monitors (frontend)" kubernetes.core.k8s: definition: "{{ lookup('template', 'lbaas-service-monitor.yaml.j2') }}" apply: true + state: "{{ ch_k8s_lbaas_enabled | ternary('present', 'absent') }}" validate: fail_on_error: true strict: true - when: ch_k8s_lbaas_enabled # Retry this task on failures register: k8s_apply until: k8s_apply is not failed diff --git a/nix/yk8s/k8s-supplements/ch-k8s-lbaas.nix b/nix/yk8s/k8s-supplements/ch-k8s-lbaas.nix index acd40089f01c4234d1f6d92ff5d68234b6635865..a3b311a35d81e0112bdd35f234ae48a9aa983e24 100644 --- a/nix/yk8s/k8s-supplements/ch-k8s-lbaas.nix +++ b/nix/yk8s/k8s-supplements/ch-k8s-lbaas.nix @@ -24,17 +24,28 @@ in { ]; options.yk8s.ch-k8s-lbaas = mkTopSection { + _docs.preface = '' + ``ch-k8s-lbaas`` is a LoadBalancing-solution for clusters running on OpenStack + as well as clusters running on bare metal. + Further information about it can be found here: :doc:`/user/explanation/services/ch-k8s-lbaas`. + ''; enabled = mkEnableOption "our LBaas service"; shared_secret = mkOption { description = '' + .. attention:: DEPRECATED + + This option is going to be removed soon + since the shared secret is now stored in and automatically handled via Vault. + A unique, random, base64-encoded secret. To generate such a secret, you can use the following command: $ dd if=/dev/urandom bs=16 count=1 status=none | base64 ''; # type as per https://pkg.go.dev/encoding/base64#StdEncoding # (ch-k8s-lbaas uses that library) - type = types.yk8s.encoding.base64Str; + type = types.nullOr types.yk8s.encoding.base64Str; example = "Example+NZHrRAV9AAN83T7Hc6wVk9IGzPou6UjwWhL+4hu1I4XPj+YG/AgKiFIc1a1EzmQKax9VAj6P/oA45w=="; + default = null; }; version = mkOption { type = types.yk8s.oci.imageTag; @@ -170,6 +181,21 @@ in { else v; }; }; + config.yk8s.warnings = + [ + ] + ++ lib.optional (cfg.enabled && cfg.shared_secret != null) '' + config.yk8s.ch-k8s-lbaas.shared_secret: is deprecated. + The option will be removed in a future release. + + You have two options: + a) You want to keep the currently configured shared secret. + The configured shared secret is automatically moved to Vault on a rollout. + After a rollout has been done, you can unset this option. + b) You do not care about the shared secret. + You can unset this option. + A shared secret will be automatically generated and stored in Vault on a rollout. + ''; config.yk8s.assertions = [ # Due to OVN support, require version >= 0.8.0 (warn only if not in semver2 format) ( @@ -224,7 +250,7 @@ in { inherit cfg; ansible_prefix = "ch_k8s_lbaas_"; inventory_path = "all/ch-k8s-lbaas.yaml"; - only_if_enabled = true; + only_if_enabled = false; }) ]; } diff --git a/tools/vault/init.sh b/tools/vault/init.sh index a6797409af32f5f0806b72cfddf2b2377e9aebcc..6eff2318a42672e2734a1fabb95d8095e9feb73f 100755 --- a/tools/vault/init.sh +++ b/tools/vault/init.sh @@ -193,6 +193,10 @@ path "$common_path_prefix/{{ identity.entity.aliases.$nodes_approle_accessor.met path "$common_path_prefix/+/kv/data/ipsec-eap-psk" { capabilities = ["read"] } + +path "$common_path_prefix/+/kv/data/ch-k8s-lbaas-shared-secret" { + capabilities = ["read"] +} EOF write_policy gateway <