diff --git a/filter_plugins/csp_filters.py b/filter_plugins/csp_filters.py index 5eed8beb..eae55f26 100644 --- a/filter_plugins/csp_filters.py +++ b/filter_plugins/csp_filters.py @@ -131,14 +131,18 @@ class FilterModule(object): flags = self.get_csp_flags(applications, application_id, directive) tokens += flags - # Matomo integration - if ( - self.is_feature_enabled(applications, matomo_feature_name, application_id) - and directive in ['script-src-elem', 'connect-src'] - ): - matomo_domain = domains.get('web-app-matomo')[0] - if matomo_domain: - tokens.append(f"{web_protocol}://{matomo_domain}") + + if directive in ['script-src-elem', 'connect-src']: + # Matomo integration + if self.is_feature_enabled(applications, matomo_feature_name, application_id): + matomo_domain = domains.get('web-app-matomo')[0] + if matomo_domain: + tokens.append(f"{web_protocol}://{matomo_domain}") + + # Allow the loading of js from the cdn + if self.is_feature_enabled(applications, 'logout', application_id) or self.is_feature_enabled(applications, 'desktop', application_id): + domain = domains.get('web-svc-cdn')[0] + tokens.append(f"{web_protocol}://{domain}") # ReCaptcha integration: allow loading scripts from Google if feature enabled if self.is_feature_enabled(applications, 'recaptcha', application_id): @@ -146,12 +150,6 @@ class FilterModule(object): tokens.append('https://www.gstatic.com') tokens.append('https://www.google.com') - # Allow the loading of js from the cdn - if directive == 'script-src-elem': - if self.is_feature_enabled(applications, 'logout', application_id) or self.is_feature_enabled(applications, 'desktop', application_id): - domain = domains.get('web-svc-cdn')[0] - tokens.append(f"{domain}") - if directive == 'frame-ancestors': # Enable loading via ancestors if self.is_feature_enabled(applications, 'desktop', application_id): @@ -163,11 +161,11 @@ class FilterModule(object): # Allow logout via infinito logout proxy domain = domains.get('web-svc-logout')[0] - tokens.append(f"{domain}") + tokens.append(f"{web_protocol}://{domain}") # Allow logout via keycloak app domain = domains.get('web-app-keycloak')[0] - tokens.append(f"{domain}") + tokens.append(f"{web_protocol}://{domain}") # whitelist tokens += self.get_csp_whitelist(applications, application_id, directive) diff --git a/roles/web-app-akaunting/Todo.md b/roles/web-app-akaunting/Todo.md new file mode 100644 index 00000000..6af9e1ae --- /dev/null +++ b/roles/web-app-akaunting/Todo.md @@ -0,0 +1,3 @@ +# To-dos +- Enable redis +- Enable OIDC \ No newline at end of file diff --git a/roles/web-app-akaunting/config/main.yml b/roles/web-app-akaunting/config/main.yml index c828f58d..8d6ad2e4 100644 --- a/roles/web-app-akaunting/config/main.yml +++ b/roles/web-app-akaunting/config/main.yml @@ -7,11 +7,26 @@ features: css: true desktop: true central_database: true - logout: true + logout: true + javascript: true server: domains: canonical: - "accounting.{{ PRIMARY_DOMAIN }}" + csp: + flags: + script-src-elem: + unsafe-inline: true + script-src: + unsafe-inline: true + unsafe-eval: true + style-src: + unsafe-inline: true + whitelist: + font-src: + - "data:" + connect-src: + - https://akaunting.com docker: services: database: @@ -22,6 +37,8 @@ docker: image: docker.io/akaunting/akaunting version: latest name: akaunting + redis: + enabled: false # Set to true to activate redis for akaunting volumes: data: akaunting_data credentials: {} diff --git a/roles/web-app-akaunting/schema/main.yml b/roles/web-app-akaunting/schema/main.yml index 2baaa7ab..319b98ff 100644 --- a/roles/web-app-akaunting/schema/main.yml +++ b/roles/web-app-akaunting/schema/main.yml @@ -2,4 +2,8 @@ credentials: setup_admin_password: description: "Initial admin user password for Akaunting" algorithm: "sha256" - validation: "^[a-f0-9]{64}$" \ No newline at end of file + validation: "^[a-f0-9]{64}$" + app_key: + description: "Laravel application key (base64 encoded 32-byte key, prefixed with 'base64:')" + algorithm: "base64_prefixed_32" + validation: "^base64:[A-Za-z0-9+/=]{43,}$" \ No newline at end of file diff --git a/roles/web-app-akaunting/tasks/main.yml b/roles/web-app-akaunting/tasks/main.yml index b67446da..c582c9e6 100644 --- a/roles/web-app-akaunting/tasks/main.yml +++ b/roles/web-app-akaunting/tasks/main.yml @@ -1,18 +1,24 @@ --- +- name: "Akaunting | Check if first run (marker exists?)" + ansible.builtin.stat: + path: "{{ AKAUNTING_SETUP_MARKER }}" + register: akaunting_marker_stat + +- name: "Akaunting | Decide if setup should be enabled" + ansible.builtin.set_fact: + akaunting_setup_enabled: "{{ not akaunting_marker_stat.stat.exists }}" + - name: "For '{{ application_id }}': load docker, db and proxy" - include_role: + include_role: name: cmp-db-docker-proxy - -- name: "include tasks update-repository-with-files.yml" - include_tasks: utils/update-repository-with-files.yml vars: - detached_files: - - "docker-compose.yml" + # Forward flag into compose templating + cmp_extra_facts: + akaunting_setup_enabled: "{{ akaunting_setup_enabled }}" -- name: "For '{{ application_id }}': create {{ docker_compose.files.env }}" - template: - src: "env.j2" - dest: "{{ docker_compose.files.env }}" - mode: "0770" - force: yes - notify: docker compose up +- name: "Akaunting | Create first-run marker to disable future setup" + ansible.builtin.file: + path: "{{ AKAUNTING_SETUP_MARKER }}" + state: touch + mode: "0644" + when: akaunting_setup_enabled | bool \ No newline at end of file diff --git a/roles/web-app-akaunting/templates/docker-compose.yml.j2 b/roles/web-app-akaunting/templates/docker-compose.yml.j2 index 354f21cb..82cd71e6 100644 --- a/roles/web-app-akaunting/templates/docker-compose.yml.j2 +++ b/roles/web-app-akaunting/templates/docker-compose.yml.j2 @@ -2,22 +2,26 @@ application: {% include 'roles/docker-container/templates/base.yml.j2' %} - container_name: {{ akaunting_name }} - image: "{{ akaunting_image }}:{{ akaunting_version }}" +{% set container_port = 80 %} + container_name: {{ AKAUNTING_CONTAINER }} + image: "{{ AKAUNTING_IMAGE }}:{{ AKAUNTING_VERSION }}" build: - context: . + context: {{ docker_repository_path }} + dockerfile: Dockerfile ports: - - 127.0.0.1:{{ ports.localhost.http[application_id] }}:80 + - 127.0.0.1:{{ ports.localhost.http[application_id] }}:{{ container_port }} volumes: - data:/var/www/html +{% if akaunting_setup_enabled | bool %} environment: - - AKAUNTING_SETUP + - AKAUNTING_SETUP=true +{% endif %} +{% include 'roles/docker-container/templates/healthcheck/tcp.yml.j2' %} {% include 'roles/docker-container/templates/networks.yml.j2' %} {% include 'roles/docker-container/templates/depends_on/dmbs_excl.yml.j2' %} {% include 'roles/docker-compose/templates/volumes.yml.j2' %} data: - name: {{ akaunting_volume }} - + name: {{ AKAUNTING_VOLUME }} {% include 'roles/docker-compose/templates/networks.yml.j2' %} \ No newline at end of file diff --git a/roles/web-app-akaunting/templates/env.j2 b/roles/web-app-akaunting/templates/env.j2 index 753d9491..61bdb840 100644 --- a/roles/web-app-akaunting/templates/env.j2 +++ b/roles/web-app-akaunting/templates/env.j2 @@ -1,22 +1,55 @@ -# You should change this to match your reverse proxy DNS name and protocol -APP_URL={{ domains | get_url(application_id, WEB_PROTOCOL) }} +# https://github.com/akaunting/akaunting/blob/master/.env.example +APP_URL={{ AKAUNTING_URL }} + +# Locales LOCALE={{ HOST_LL }} +TIMEZONE={{ HOST_TIMEZONE }} + +# Environment +APP_DEBUG={{ MODE_DEBUG | lower }} +APP_ENV={{ ENVIRONMENT }} # Don't change this unless you rename your database container or use rootless podman, in case of using rootless podman you should set it to 127.0.0.1 (NOT localhost) DB_HOST={{ database_host }} - -# Change these to match env/db.env DB_DATABASE={{ database_name }} DB_USERNAME={{ database_username }} DB_PASSWORD={{ database_password }} - -# You should change this to a random string of three numbers or letters followed by an underscore +DB_PORT={{ database_port }} +DB_CONNECTION=mysql DB_PREFIX=asd_ +# Proxy +TRUSTED_PROXIES=* +TRUSTED_HEADERS=X_FORWARDED_FOR,X_FORWARDED_HOST,X_FORWARDED_PORT,X_FORWARDED_PROTO + # These define the first company to exist on this instance. They are only used during setup. -COMPANY_NAME={{applications | get_app_conf(application_id, 'company.name', True)}} -COMPANY_EMAIL={{applications | get_app_conf(application_id, 'company.email', True)}} +COMPANY_NAME={{ AKAUNTING_COMPANY_NAME }} +COMPANY_EMAIL={{ AKAUNTING_COMPANY_EMAIL }} + +# Credentials +APP_KEY={{ AKAUNTING_APP_KEY }} # This will be the first administrative user created on setup. -ADMIN_EMAIL={{applications.akaunting.setup_admin_email}} -ADMIN_PASSWORD={{applications | get_app_conf(application_id, 'credentials.setup_admin_password', True)}} +ADMIN_EMAIL={{ AKAUNTING_ADMIN_EMAIL }} +ADMIN_PASSWORD={{ AKAUNTING_ADMIN_PASSWORD }} + +# Cache +CACHE_DRIVER={{ AKAUNTING_CACHE_DRIVER }} +SESSION_DRIVER={{ AKAUNTING_CACHE_DRIVER }} +QUEUE_CONNECTION={{ 'sync' if AKAUNTING_CACHE_DRIVER == 'file' else AKAUNTING_CACHE_DRIVER }} +{% if AKAUNTING_CACHE_DRIVER == 'redis' %} +REDIS_CLIENT=phpredis +REDIS_HOST=redis +REDIS_PASSWORD=null +REDIS_PORT=6379 +{% endif %} + +# Email +MAIL_MAILER={{ 'smtp' if SYSTEM_EMAIL.SMTP else 'sendmail' }} +MAIL_HOST={{ SYSTEM_EMAIL.HOST }} +MAIL_PORT={{ SYSTEM_EMAIL.PORT }} +MAIL_USERNAME={{ users['no-reply'].email }} +MAIL_PASSWORD={{ users['no-reply'].mailu_token }} +MAIL_ENCRYPTION={{ SYSTEM_EMAIL.TLS | ternary("tls","null") }} +MAIL_FROM_ADDRESS={{ AKAUNTING_COMPANY_EMAIL }} +MAIL_FROM_NAME={{ AKAUNTING_COMPANY_NAME }} diff --git a/roles/web-app-akaunting/templates/javascript.js.j2 b/roles/web-app-akaunting/templates/javascript.js.j2 new file mode 100644 index 00000000..26bb609c --- /dev/null +++ b/roles/web-app-akaunting/templates/javascript.js.j2 @@ -0,0 +1 @@ +{% include 'templates/roles/web-app/templates/javascripts/sso_warning.js.j2' %} diff --git a/roles/web-app-akaunting/vars/main.yml b/roles/web-app-akaunting/vars/main.yml index 2ae57a51..b150635f 100644 --- a/roles/web-app-akaunting/vars/main.yml +++ b/roles/web-app-akaunting/vars/main.yml @@ -1,8 +1,28 @@ -application_id: "web-app-akaunting" -database_type: "mariadb" -database_password: "{{ applications | get_app_conf(application_id, 'credentials.database_password', True) }}" -docker_repository_address: "https://github.com/akaunting/docker.git" -akaunting_version: "{{ applications | get_app_conf(application_id, 'docker.services.akaunting.version', True) }}" -akaunting_image: "{{ applications | get_app_conf(application_id, 'docker.services.akaunting.image', True) }}" -akaunting_name: "{{ applications | get_app_conf(application_id, 'docker.services.akaunting.name', True) }}" -akaunting_volume: "{{ applications | get_app_conf(application_id, 'docker.volumes.data', True) }}" \ No newline at end of file + +# General +application_id: "web-app-akaunting" +js_application_name: "Akaunting" + +# Database +database_type: "mariadb" +database_password: "{{ applications | get_app_conf(application_id, 'credentials.database_password') }}" + +# Docker +docker_repository_address: "https://github.com/akaunting/docker.git" +docker_pull_git_repository: true +docker_repository_branch: "master" +docker_compose_skipp_file_creation: false + +# Akaunting +AKAUNTING_URL: "{{ domains | get_url(application_id, WEB_PROTOCOL) }}" +AKAUNTING_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.akaunting.version') }}" +AKAUNTING_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.akaunting.image') }}" +AKAUNTING_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.akaunting.name') }}" +AKAUNTING_VOLUME: "{{ applications | get_app_conf(application_id, 'docker.volumes.data') }}" +AKAUNTING_COMPANY_NAME: "{{ applications | get_app_conf(application_id, 'company.name') }}" +AKAUNTING_COMPANY_EMAIL: "{{ applications | get_app_conf(application_id, 'company.email') }}" +AKAUNTING_ADMIN_EMAIL: "{{ applications | get_app_conf(application_id, 'setup_admin_email') }}" +AKAUNTING_ADMIN_PASSWORD: "{{ applications | get_app_conf(application_id, 'credentials.setup_admin_password') }}" +AKAUNTING_SETUP_MARKER: "/var/lib/docker/volumes/{{ AKAUNTING_VOLUME }}/_data/.akaunting_installed" +AKAUNTING_APP_KEY: "{{ applications | get_app_conf(application_id, 'credentials.app_key') }}" +AKAUNTING_CACHE_DRIVER: "{{ 'redis' if applications | get_app_conf(application_id, 'docker.services.redis.enabled') else 'file' }}" \ No newline at end of file diff --git a/tasks/utils/update-repository-with-files.yml b/tasks/utils/update-repository-with-files.yml deleted file mode 100644 index afd8b1f4..00000000 --- a/tasks/utils/update-repository-with-files.yml +++ /dev/null @@ -1,38 +0,0 @@ -# It isn't best practice to use this task -# Better load the repositories into /opt/docker/[servicename]/services, build them there and then use a docker-compose file for customizing -# @todo Refactor\Remove -# @deprecated -- name: "Merge detached_files with applications | get_app_conf('web-app-oauth2-proxy','configuration_file')" - set_fact: - merged_detached_files: "{{ detached_files + [applications | get_app_conf('web-app-oauth2-proxy','configuration_file')] }}" - when: "{{ applications | get_app_conf(application_id,'features.oauth2')" - -- name: "backup detached files" - command: > - mv "{{ docker_compose.directories.instance }}{{ item }}" "/tmp/{{ application_id }}-{{ item }}.backup" - args: - removes: "{{ docker_compose.directories.instance }}{{ item }}" - become: true - loop: "{{ merged_detached_files | default(detached_files) }}" - -- name: checkout repository - ansible.builtin.shell: git checkout . - become: true - args: - chdir: "{{ docker_compose.directories.instance }}" - ignore_errors: true - -- name: "restore detached files" - command: > - mv "/tmp/{{ application_id }}-{{ item }}.backup" "{{ docker_compose.directories.instance }}{{ item }}" - args: - removes: "/tmp/{{ application_id }}-{{ item }}.backup" - become: true - loop: "{{ merged_detached_files | default(detached_files) }}" - -- name: "copy {{ detached_files }} templates to server" - template: - src: "{{ item }}.j2" - dest: "{{ docker_compose.directories.instance }}{{ item }}" - loop: "{{ detached_files }}" - notify: docker compose up