commit 494e748beb7989ec1f896a5a65caad52b62199d8 Author: Paul Breugnot Date: Mon Oct 19 14:24:03 2026 +0200 feat: Base rainbowsheep-cloud ansible playbooks and roles diff --git a/README.md b/README.md new file mode 100644 index 0000000..d914a66 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# Rainbowsheep Cloud Server :sheep: :rainbow: + +Ansible recipes to install and maintain an instance of the Rainbowsheep cloud. + +The Rainbowsheep cloud is a very lightweight and secured data cloud based on +standard or widely available tools such as NFS, Kerberos and gocryptfs. + +It is an alternative to heavy cloud infrastructure such as NextCloud that +requires databases, web clients and so on. + +The Rainbowsheep cloud is accessible as a regular Linux file system. This allows +to easily connect to the cloud with standard Linux tools, and to easily and +efficiently perfom complex operations on files if required. + +## Server structure + +Data for all users are stored in the following directories: +``` +/srv +├── protected +│ ├── user1 +│ ├── user2 +│ └── ... +└── vault + ├── user1 + ├── user2 + └── ... +``` + +Data in `/srv/protected` is stored as plain files on the server, so this +endpoint must be served using `krb5p`, i.e. Kerberos authentication and +encryption. This means your data is securely transmitted and other users cannot +have access to your data, but a `root` user on the server can read it. + +Data in `/srv/vault` is encrypted at the filesystem level using `gocryptfs`. +This means you need your own private password to access your data locally. Only +uncrypted data is visible from the server, even for `root` user. In consequence, +the `/srv/vault` endpoint can be securely served using `krb5`, i.e. Kerberos +authentication only, as transmitted data is the `gocryptfs` encrypted data. + +# Configure + +Initialize the Kerberos master password: +```bash +ansible-vault encrypt_string --ask-vault-pass --stdin-name "krb5_master_password" --output krb5_master_password.yaml +``` + +# Install + +Install cloud using Ansible: +```bash +ansible-playbook -i inventory -e "@krb5_master_password.yml" --ask-vault-pass rainbowsheep-cloud.yml +``` + +# Create user + +```bash +export RAINBOW_NEW_USER=... +``` + +Initialize user password: +```bash +ansible-vault encrypt_string --ask-vault-pass --stdin-name "password" --output $RAINBOW_NEW_USER.yml +echo "username=$RAINBOW_NEW_USER" >> $RAINBOW_NEW_USER.yml +``` + +Deploy the new user: +```bash +ansible-playbook -i inventory -e "@$RAINBOW_NEW_USER.yml" create-user.yml +``` + +Once created, the specified password can be used on client side for Kerberos +authentication (`kinit`) and to mount the `gocryptfs` vault. diff --git a/create-user.yml b/create-user.yml new file mode 100644 index 0000000..4eb46ca --- /dev/null +++ b/create-user.yml @@ -0,0 +1,65 @@ +- name: Create new Rainbosheep cloud user + hosts: server + tasks: + - name: Create kerberos user + ansible.builtin.command: + cmd: "kadmin.local add_principal -pw '{{ password }}' {{ username }}" + register: create_krb_user_out + failed_when: create_krb_user_out.rc != 0 and create_krb_user_out.rc != 1 + changed_when: create_krb_user_out.rc == 0 + + - name: Create linux group + ansible.builtin.group: + name: "{{ username }}" + + - name: Create linux user + ansible.builtin.user: + name: "{{ username }}" + create_home: false + group: "{{ username }}" + + - name: Create user protected repository + ansible.builtin.file: + path: "/srv/protected/{{ username }}" + state: directory + owner: "{{ username }}" + group: "{{ username }}" + mode: "770" + + - name: Create user vault repository + ansible.builtin.file: + path: "/srv/vault/{{ username }}" + state: directory + owner: "{{ username }}" + group: "{{ username }}" + mode: "770" + + - name: Init gocryptfs vault + ansible.builtin.command: + cmd: "gocryptfs --init /srv/vault/{{ username }}" + stdin: "{{ password }}\n" + register: gocryptfs_out + failed_when: gocryptfs_out.rc != 0 and gocryptfs_out.rc != 6 + changed_when: gocryptfs_out.rc == 0 + + - name: Get gocryptfs master key + ansible.builtin.command: + cmd: "gocryptfs-xray -dumpmasterkey /srv/vault/{{ username }}/gocryptfs.conf" + stdin: "{{ password }}\n" + register: master_key + + - name: Print master key + ansible.builtin.debug: + msg: | + Your master key is: + + {{ master_key.stdout }} + + If the gocryptfs.conf file becomes corrupted or you ever forget your password, + there is only one hope for recovery: The master key. Print it to a piece of + paper and store it in a drawer. + + - name: Log master key + ansible.builtin.debug: + msg: "{{ gocryptfs_out }}" + diff --git a/rainbowsheep-cloud.yml b/rainbowsheep-cloud.yml new file mode 100644 index 0000000..379afc4 --- /dev/null +++ b/rainbowsheep-cloud.yml @@ -0,0 +1,8 @@ + +- name: Rainbowsheep Cloud Server + hosts: server + roles: + - chrony + - kerberos + - nfs + - vault diff --git a/roles/chrony/tasks/main.yml b/roles/chrony/tasks/main.yml new file mode 100644 index 0000000..c440c02 --- /dev/null +++ b/roles/chrony/tasks/main.yml @@ -0,0 +1,22 @@ +--- +# tasks file for chrony_client +- name: Install chrony + ansible.builtin.apt: + name: chrony + state: present + update_cache: yes + +# - name: Configure chrony client + # ansible.builtin.template: + # src: chrony.conf.j2 + # dest: /etc/chrony/chrony.conf + # owner: root + # group: root + # mode: "0644" + # notify: Restart chrony + +- name: Enable and start chrony + ansible.builtin.service: + name: chrony + state: started + enabled: yes diff --git a/roles/kerberos/defaults/main.yml b/roles/kerberos/defaults/main.yml new file mode 100644 index 0000000..5868e6a --- /dev/null +++ b/roles/kerberos/defaults/main.yml @@ -0,0 +1,2 @@ + +kerberos_realm: "{{ ansible_facts['hostname'] }}" diff --git a/roles/kerberos/handlers/main.yml b/roles/kerberos/handlers/main.yml new file mode 100644 index 0000000..4b7b3ee --- /dev/null +++ b/roles/kerberos/handlers/main.yml @@ -0,0 +1,8 @@ +- name: Restart krb5 kdc + ansible.builtin.service: + name: krb5-kdc + state: restarted +- name: Restart krb5 admin server + ansible.builtin.service: + name: krb5-admin-server + state: restarted diff --git a/roles/kerberos/tasks/main.yml b/roles/kerberos/tasks/main.yml new file mode 100644 index 0000000..2f826e8 --- /dev/null +++ b/roles/kerberos/tasks/main.yml @@ -0,0 +1,76 @@ +- name: Install Kerberos server + ansible.builtin.apt: + pkg: + - krb5-user + - krb5-kdc + - krb5-admin-server + state: present + update_cache: yes + +- name: Enables krb5-kdc + ansible.builtin.service: + name: krb5-kdc + enabled: true + +- name: Enables krb5-admin-server + ansible.builtin.service: + name: krb5-admin-server + enabled: true + +- name: Configure krb5 + ansible.builtin.template: + src: krb5.conf.j2 + dest: /etc/krb5.conf + owner: root + group: root + mode: "0644" + notify: + - Restart krb5 kdc + - Restart krb5 admin server + +- name: Configure krb5 kdc + ansible.builtin.template: + src: kdc.conf.j2 + dest: /etc/krb5kdc/kdc.conf + owner: root + group: root + mode: "0644" + notify: + - Restart krb5 kdc + - Restart krb5 admin server + +- name: Configure krb5 ACLs + # Allow */admin (notable root/admin) user to manage principals + ansible.builtin.template: + src: kadm5.acl.j2 + dest: /etc/krb5kdc/kadm5.acl + owner: root + group: root + mode: "0644" + notify: + - Restart krb5 kdc + - Restart krb5 admin server + + +- name: Create kdc + ansible.builtin.command: + cmd: "kdb5_util create -P {{ krb5_master_password }}" + register: create_kdc + failed_when: create_kdc.rc != 0 and create_kdc.rc != 1 + changed_when: create_kdc.rc == 0 + +- name: Create kdc stash file + # The stash file is specified in kdc.conf as key_stash_file + ansible.builtin.command: + cmd: "kdb5_util stash -P {{ krb5_master_password }}" + +- name: Start krb5-kdc + ansible.builtin.service: + name: krb5-kdc + state: started + +- name: Start krb5-admin-server + ansible.builtin.service: + name: krb5-admin-server + state: started + diff --git a/roles/kerberos/templates/kadm5.acl.j2 b/roles/kerberos/templates/kadm5.acl.j2 new file mode 100644 index 0000000..14b08f2 --- /dev/null +++ b/roles/kerberos/templates/kadm5.acl.j2 @@ -0,0 +1 @@ +*/admin@{{ kerberos_realm | upper }} * diff --git a/roles/kerberos/templates/kdc.conf.j2 b/roles/kerberos/templates/kdc.conf.j2 new file mode 100644 index 0000000..e71b601 --- /dev/null +++ b/roles/kerberos/templates/kdc.conf.j2 @@ -0,0 +1,21 @@ +[kdcdefaults] + kdc_ports = 750,88 + +[realms] + {{ kerberos_realm | upper }} = { + database_name = /var/lib/krb5kdc/principal + admin_keytab = FILE:/etc/krb5kdc/kadm5.keytab + acl_file = /etc/krb5kdc/kadm5.acl + key_stash_file = /etc/krb5kdc/stash + kdc_ports = 750,88 + max_life = 3d 0h 0m 0s + max_renewable_life = 7d 0h 0m 0s + #master_key_type = aes256-cts + #supported_enctypes = aes256-cts:normal aes128-cts:normal + default_principal_flags = +preauth + } + +[logging] + kdc = CONSOLE + kdc = FILE:/var/log/kdc.log + debug = true diff --git a/roles/kerberos/templates/krb5.conf.j2 b/roles/kerberos/templates/krb5.conf.j2 new file mode 100644 index 0000000..d7062cf --- /dev/null +++ b/roles/kerberos/templates/krb5.conf.j2 @@ -0,0 +1,28 @@ +[libdefaults] + default_realm = {{ kerberos_realm | upper }} + dns_lookup_realm = true + dns_lookup_kdc = true + +# The following krb5.conf variables are only for MIT Kerberos. + kdc_timesync = 1 + ccache_type = 4 + forwardable = true + proxiable = true + rdns = false + + ticket_lifetime = 3d + renew_lifetime = 7d + +# The following libdefaults parameters are only for Heimdal Kerberos. + fcc-mit-ticketflags = true + +[realms] + {{ kerberos_realm | upper }} = { + kdc = kerberos.{{ kerberos_realm }} + admin_server = kerberos.{{ kerberos_realm }} + default_principal_flags = +preauth + } + +[domain_realm] + {{ kerberos_realm }} = {{ kerberos_realm | upper }} + .{{ kerberos_realm }} = {{ kerberos_realm | upper }} diff --git a/roles/nfs/tasks/main.yml b/roles/nfs/tasks/main.yml new file mode 100644 index 0000000..6b35fe3 --- /dev/null +++ b/roles/nfs/tasks/main.yml @@ -0,0 +1,36 @@ +--- + +- name: Install nfs server + ansible.builtin.apt: + name: nfs-kernel-server + state: present + update_cache: yes + +- name: Create /srv/protected export + ansible.builtin.file: + state: directory + path: /srv/protected + +- name: Export /srv/protected + ansible.builtin.lineinfile: + path: /etc/exports + regexp: '^/srv/protected' + line: /srv/protected *(rw,sync,sec=krb5p,no_subtree_check) + +- name: Create /srv/vault export + ansible.builtin.file: + state: directory + path: /srv/vault + +- name: Export /srv/vault + ansible.builtin.lineinfile: + path: /etc/exports + regexp: '^/srv/vault' + line: /srv/vault *(rw,sync,sec=krb5p:krb5i:krb5,no_subtree_check) + +- name: Enable and start NFS server + ansible.builtin.service: + name: nfs-kernel-server + state: started + enabled: yes + diff --git a/roles/vault/tasks/main.yml b/roles/vault/tasks/main.yml new file mode 100644 index 0000000..aa5ec40 --- /dev/null +++ b/roles/vault/tasks/main.yml @@ -0,0 +1,5 @@ + +- name: Install gocryptfs + ansible.builtin.apt: + name: gocryptfs + update_cache: true