From 1a600b60d37e0f8a6e2e79b0e474147b5b108ae5 Mon Sep 17 00:00:00 2001 From: Kris Date: Wed, 16 Sep 2026 18:55:02 +0000 Subject: [PATCH] feat: make the server config file mode configurable (#561) * feat: make the server config file mode configurable /etc/rancher/k3s/config.yaml holds the cluster token. The server and upgrade roles write it at 0644, so every local account on a server node can read the credential that joins a node to the cluster. k3s_server_config_mode sets the mode of that file in both roles. It defaults to 0644, so behaviour is unchanged unless it is set. Signed-off-by: Kristofer * docs: describe the server config file contents accurately /etc/rancher/k3s/config.yaml does not always carry the cluster token. A first server that lets K3s generate one writes the file without it, so the file can contain the token rather than always holding it. Correct that claim in the README, the inventory sample and the role default, and use the US spelling of "honors". Signed-off-by: Kristofer * Name the floor the config file mode can be narrowed to Saying any mode root can read works leaves the reader to work out what that means in practice. The file is read only by k3s, running as root, so 0600 is the tightest mode that still works. Signed-off-by: Kristofer * Run the basic integration job with a non-default config file mode The templated mode was only ever rendered with its default in CI, which is the one value that cannot tell a working template from a broken one. The basic inventory sets 0640 and the job reads the mode back off the installed file. Signed-off-by: Kristofer --------- Signed-off-by: Kristofer --- .github/workflows/integration.yml | 4 ++++ README.md | 10 ++++++++++ inventory-sample.yml | 1 + roles/k3s_server/defaults/main.yml | 3 +++ roles/k3s_server/tasks/main.yml | 4 ++-- roles/k3s_upgrade/defaults/main.yml | 1 + roles/k3s_upgrade/tasks/main.yml | 2 +- tests/basic.yml | 2 ++ 8 files changed, 24 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 0ff1c6d..146a7c5 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -122,6 +122,10 @@ jobs: - name: Verify K3s is running on Server run: docker exec server-node k3s kubectl get nodes | grep Ready + - name: Verify the server config file mode + if: matrix.inventory == 'basic' + run: docker exec server-node ls -l /etc/rancher/k3s/config.yaml | grep -q '^-rw-r-----' + - name: Verify K3s is running on Agent (systemd) if: matrix.service_mgr == 'systemd' run: docker exec agent-node systemctl status k3s-agent | grep running diff --git a/README.md b/README.md index c9bd45b..e901f57 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,16 @@ The `use_external_database` flag is required when more than one server is define The format of the datastore-endpoint parameter is dependent upon the datastore backend, please visit the [K3s datastore endpoint format](https://docs.k3s.io/datastore#datastore-endpoint-format-and-functionality) for details on the format and supported datastores. +### Server config file permissions + +The server role writes `/etc/rancher/k3s/config.yaml` at mode `0644`. That file can contain the cluster token, so any local account on a server node can read the credential that joins a node to the cluster. Set `k3s_server_config_mode` to narrow it: + +```yaml +k3s_server_config_mode: "0640" +``` + +K3s reads the file as root, so `0600` is the safe floor: no other account needs the file. The default stays `0644`, which is the mode the file already has on a running cluster, so a re-run changes nothing until the variable is set. The upgrade role rewrites the same file and honors the same variable. + ## Upgrading A playbook is provided to upgrade K3s on all nodes in the cluster. To use it, update `k3s_version` with the desired version in `inventory.yml` and run one of the following commands. Again, the syntax is slightly different depending on whether you installed `k3s-ansible` with `ansible-galaxy` or if you run the playbook from within the cloned git repository: diff --git a/inventory-sample.yml b/inventory-sample.yml index 189b950..d45b828 100644 --- a/inventory-sample.yml +++ b/inventory-sample.yml @@ -34,6 +34,7 @@ k3s_cluster: # extra_service_envs: [ 'ENV_VAR1=VALUE1', 'ENV_VAR2=VALUE2' ] # user_kubectl: true, by default kubectl is symlinked and configured for use by ansible_user. Set to false to only kubectl via root user. # manage_firewall: false + # k3s_server_config_mode: "0644", mode of /etc/rancher/k3s/config.yaml on server nodes. The file can contain the cluster token; set "0640" to keep it off other local accounts. # Manifests or Airgap should be either full paths or relative to the playbook directory. # List of locally available manifests to apply to the cluster, useful for PVCs or Traefik modifications. diff --git a/roles/k3s_server/defaults/main.yml b/roles/k3s_server/defaults/main.yml index 058aff1..0bcf38a 100644 --- a/roles/k3s_server/defaults/main.yml +++ b/roles/k3s_server/defaults/main.yml @@ -1,5 +1,8 @@ --- k3s_server_location: "/var/lib/rancher/k3s" +# /etc/rancher/k3s/config.yaml can contain the cluster token, so a cluster +# that does not want it world readable can narrow this to "0640". +k3s_server_config_mode: "0644" systemd_dir: "/etc/systemd/system" # noqa var-naming[no-role-prefix] api_port: 6443 # noqa var-naming[no-role-prefix] kubeconfig: ~/.kube/config.new # noqa var-naming[no-role-prefix] diff --git a/roles/k3s_server/tasks/main.yml b/roles/k3s_server/tasks/main.yml index afa4358..aedeefb 100644 --- a/roles/k3s_server/tasks/main.yml +++ b/roles/k3s_server/tasks/main.yml @@ -152,7 +152,7 @@ ansible.builtin.copy: content: "{{ k3s_server_config | to_nice_yaml }}" dest: "/etc/rancher/k3s/config.yaml" - mode: "0644" + mode: "{{ k3s_server_config_mode }}" register: k3s_server_config_result - name: Delete any existing token from the environment if different from the new one @@ -280,7 +280,7 @@ ansible.builtin.copy: content: "{{ k3s_server_config | to_nice_yaml }}" dest: "/etc/rancher/k3s/config.yaml" - mode: "0644" + mode: "{{ k3s_server_config_mode }}" register: k3s_server_config_result - name: Delete any existing token from the environment if different from the new one diff --git a/roles/k3s_upgrade/defaults/main.yml b/roles/k3s_upgrade/defaults/main.yml index 99ae181..6675cc2 100644 --- a/roles/k3s_upgrade/defaults/main.yml +++ b/roles/k3s_upgrade/defaults/main.yml @@ -8,4 +8,5 @@ api_port: 6443 # noqa var-naming[no-role-prefix] extra_server_args: "" # noqa var-naming[no-role-prefix] extra_agent_args: "" # noqa var-naming[no-role-prefix] use_external_database: false # noqa var-naming[no-role-prefix] +k3s_server_config_mode: "0644" # noqa var-naming[no-role-prefix] # api_endpoint must be provided by inventory for agent nodes diff --git a/roles/k3s_upgrade/tasks/main.yml b/roles/k3s_upgrade/tasks/main.yml index 84e55b4..91ca714 100644 --- a/roles/k3s_upgrade/tasks/main.yml +++ b/roles/k3s_upgrade/tasks/main.yml @@ -116,7 +116,7 @@ ansible.builtin.copy: content: "{{ k3s_server_config | to_nice_yaml }}" dest: "/etc/rancher/k3s/config.yaml" - mode: "0644" + mode: "{{ k3s_server_config_mode }}" register: k3s_server_config_result - name: Install new K3s Version [server] diff --git a/tests/basic.yml b/tests/basic.yml index 406141b..7a7f189 100644 --- a/tests/basic.yml +++ b/tests/basic.yml @@ -15,5 +15,7 @@ k3s_cluster: k3s_version: v1.33.4+k3s1 token: "secret12345" api_endpoint: "server-node" + # A mode other than the default, so the templated mode is exercised. + k3s_server_config_mode: "0640" extra_server_args: "--snapshotter=native" extra_agent_args: "--snapshotter=native"