From e56085f038a5a76556383af5e907a6a0b7a104aa Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Thu, 11 Dec 2025 13:32:55 +0100 Subject: [PATCH 01/16] Add myaccessid proxy --- roles/midproxy/defaults/main.yml | 8 + roles/midproxy/files/internal_attributes.yaml | 27 ++ .../plugins/backends/openid_backend.yaml | 14 + .../files/plugins/backends/saml2_backend.yaml | 1 + .../plugins/frontends/ping_frontend.yaml | 3 + .../plugins/frontends/saml2_frontend.yaml | 63 +++++ .../microservices/static_attributes.yaml | 5 + roles/midproxy/files/proxy_conf.yaml | 73 +++++ roles/midproxy/tasks/main.yml | 56 ++++ roles/redis/defaults/main.yml | 11 + roles/redis/handlers/main.yml | 6 + roles/redis/tasks/main.yml | 61 ++++ roles/redis/templates/redis.conf.j2 | 3 + roles/redis/vars/main.yml | 1 + roles/sbs/defaults/main.yml | 159 +++++++++++ roles/sbs/files/yarn.gpg | 243 ++++++++++++++++ roles/sbs/handlers/main.yml | 9 + roles/sbs/tasks/main.yml | 171 ++++++++++++ roles/sbs/templates/alembic.ini.j2 | 72 +++++ roles/sbs/templates/config.yml.j2 | 264 ++++++++++++++++++ roles/sbs/templates/disclaimer.css.j2 | 6 + .../templates/saml_advanced_settings.json.j2 | 35 +++ roles/sbs/templates/saml_settings.json.j2 | 22 ++ roles/sbs/templates/sbs-apache.conf.j2 | 30 ++ roles/sbs/templates/sbs.service.j2 | 32 +++ roles/sbs/vars/main.yml | 1 + 26 files changed, 1376 insertions(+) create mode 100644 roles/midproxy/defaults/main.yml create mode 100644 roles/midproxy/files/internal_attributes.yaml create mode 100644 roles/midproxy/files/plugins/backends/openid_backend.yaml create mode 100644 roles/midproxy/files/plugins/backends/saml2_backend.yaml create mode 100644 roles/midproxy/files/plugins/frontends/ping_frontend.yaml create mode 100644 roles/midproxy/files/plugins/frontends/saml2_frontend.yaml create mode 100644 roles/midproxy/files/plugins/microservices/static_attributes.yaml create mode 100644 roles/midproxy/files/proxy_conf.yaml create mode 100644 roles/midproxy/tasks/main.yml create mode 100644 roles/redis/defaults/main.yml create mode 100644 roles/redis/handlers/main.yml create mode 100644 roles/redis/tasks/main.yml create mode 100644 roles/redis/templates/redis.conf.j2 create mode 100644 roles/redis/vars/main.yml create mode 100644 roles/sbs/defaults/main.yml create mode 100644 roles/sbs/files/yarn.gpg create mode 100644 roles/sbs/handlers/main.yml create mode 100644 roles/sbs/tasks/main.yml create mode 100644 roles/sbs/templates/alembic.ini.j2 create mode 100644 roles/sbs/templates/config.yml.j2 create mode 100644 roles/sbs/templates/disclaimer.css.j2 create mode 100644 roles/sbs/templates/saml_advanced_settings.json.j2 create mode 100644 roles/sbs/templates/saml_settings.json.j2 create mode 100644 roles/sbs/templates/sbs-apache.conf.j2 create mode 100644 roles/sbs/templates/sbs.service.j2 create mode 100644 roles/sbs/vars/main.yml diff --git a/roles/midproxy/defaults/main.yml b/roles/midproxy/defaults/main.yml new file mode 100644 index 000000000..3522fcb47 --- /dev/null +++ b/roles/midproxy/defaults/main.yml @@ -0,0 +1,8 @@ +--- +midproxy: + satosa_version: 8 + state_encryption_key: 'secret' + issuer: 'issuer' + client_id: 'client' + client_secret: 'secret' + sp_metadata: 'eb-metadata.xml' diff --git a/roles/midproxy/files/internal_attributes.yaml b/roles/midproxy/files/internal_attributes.yaml new file mode 100644 index 000000000..f03873371 --- /dev/null +++ b/roles/midproxy/files/internal_attributes.yaml @@ -0,0 +1,27 @@ +attributes: + displayname: + openid: [name] + saml: [displayName] + edupersontargetedid: + openid: [sub] + saml: [eduPersonTargetedID] + givenname: + openid: [given_name] + saml: [givenName] + mail: + openid: [email] + saml: [email, emailAddress, mail] + name: + openid: [name] + saml: [cn] + surname: + openid: [family_name] + saml: [sn, surname] + uid: + openid: [sub] + saml: [uid] + schachomeorganization: + openid: [schac_home_organization] + saml: [schacHomeOrganization] +user_id_from_attrs: [edupersontargetedid] +user_id_to_attr: edupersontargetedid diff --git a/roles/midproxy/files/plugins/backends/openid_backend.yaml b/roles/midproxy/files/plugins/backends/openid_backend.yaml new file mode 100644 index 000000000..cb78fcccd --- /dev/null +++ b/roles/midproxy/files/plugins/backends/openid_backend.yaml @@ -0,0 +1,14 @@ +module: satosa.backends.openid_connect.OpenIDConnectBackend +name: myaccessid +config: + provider_metadata: + issuer: !ENV SATOSA_ISSUER + client: + verify_ssl: yes + auth_req_params: + response_type: code + scope: [openid, profile, email, schac_home_organization] + client_metadata: + client_id: !ENV SATOSA_CLIENT_ID + client_secret: !ENV SATOSA_CLIENT_SECRET + redirect_uris: [/] diff --git a/roles/midproxy/files/plugins/backends/saml2_backend.yaml b/roles/midproxy/files/plugins/backends/saml2_backend.yaml new file mode 100644 index 000000000..ed97d539c --- /dev/null +++ b/roles/midproxy/files/plugins/backends/saml2_backend.yaml @@ -0,0 +1 @@ +--- diff --git a/roles/midproxy/files/plugins/frontends/ping_frontend.yaml b/roles/midproxy/files/plugins/frontends/ping_frontend.yaml new file mode 100644 index 000000000..c09b218b6 --- /dev/null +++ b/roles/midproxy/files/plugins/frontends/ping_frontend.yaml @@ -0,0 +1,3 @@ +module: satosa.frontends.ping.PingFrontend +name: ping +config: null diff --git a/roles/midproxy/files/plugins/frontends/saml2_frontend.yaml b/roles/midproxy/files/plugins/frontends/saml2_frontend.yaml new file mode 100644 index 000000000..059b5bc60 --- /dev/null +++ b/roles/midproxy/files/plugins/frontends/saml2_frontend.yaml @@ -0,0 +1,63 @@ +module: satosa.frontends.saml2.SAMLFrontend +name: idp +config: + #acr_mapping: + # "": "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified" + # "https://accounts.google.com": "http://eidas.europa.eu/LoA/low" + + endpoints: + single_sign_on_service: + 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST': sso/post + 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect': sso/redirect + + # If configured and not false or empty the common domain cookie _saml_idp will be set + # with or have appended the IdP used for authentication. The default is not to set the + # cookie. If the value is a dictionary with key 'domain' then the domain for the cookie + # will be set to the value for the 'domain' key. If no 'domain' is set then the domain + # from the BASE defined for the proxy will be used. + #common_domain_cookie: + # domain: .example.com + + entityid_endpoint: true + enable_metadata_reload: no + + idp_config: + organization: {display_name: SURF, name: SURF, url: 'https://www.surf.nl/'} + contact_person: + - {contact_type: technical, email_address: 'mailto:sram-beheer@surf.nl', given_name: Technical} + - {contact_type: support, email_address: 'mailto:sram-beheer@surf.nl', given_name: Support} + - {contact_type: other, email_address: 'mailto:sram-beheer@surf.nl', given_name: Security, extension_attributes: {'xmlns:remd': 'http://refeds.org/metadata', 'remd:contactType': 'http://refeds.org/metadata/contactType/security'}} + key_file: frontend.key + cert_file: frontend.crt + metadata: + # remote: + # - url: https://engine.test2.surfconext.nl/authentication/sp/metadata + # cert: null + local: [!ENV SATOSA_SP_METADATA] + entityid: //proxy.xml + accepted_time_diff: 60 + # attribute_map_dir: plugins/attribute-maps + service: + idp: + endpoints: + single_sign_on_service: [] + name: Proxy IdP + ui_info: + display_name: + - lang: en + text: "MyAccessID proxy" + description: + - lang: en + text: "MyAccessID proxy" + keywords: + - lang: en + text: ["MyAccessID", "proxy"] + name_id_format: ['urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'] + policy: + default: + fail_on_missing_requested: false + # name_form: urn:oasis:names:tc:SAML:2.0:attrname-format:basic + # attribute_restrictions: null + # lifetime: {minutes: 15} + # encrypt_assertion: false + # encrypted_advice_attributes: false diff --git a/roles/midproxy/files/plugins/microservices/static_attributes.yaml b/roles/midproxy/files/plugins/microservices/static_attributes.yaml new file mode 100644 index 000000000..72e25bb58 --- /dev/null +++ b/roles/midproxy/files/plugins/microservices/static_attributes.yaml @@ -0,0 +1,5 @@ +module: satosa.micro_services.attribute_modifications.AddStaticAttributes +name: AddAttributes +config: + static_attributes: + schachomeorganization: 'myaccessid.org' diff --git a/roles/midproxy/files/proxy_conf.yaml b/roles/midproxy/files/proxy_conf.yaml new file mode 100644 index 000000000..550f56217 --- /dev/null +++ b/roles/midproxy/files/proxy_conf.yaml @@ -0,0 +1,73 @@ +# BASE: https://example.com +BASE: !ENV SATOSA_BASE + +COOKIE_STATE_NAME: "SATOSA_STATE" +CONTEXT_STATE_DELETE: yes +#STATE_ENCRYPTION_KEY: "asdASD123" + +cookies_samesite_compat: + - ["SATOSA_STATE", "SATOSA_STATE_LEGACY"] + +INTERNAL_ATTRIBUTES: "internal_attributes.yaml" + +BACKEND_MODULES: + - "plugins/backends/openid_backend.yaml" + +FRONTEND_MODULES: + - "plugins/frontends/saml2_frontend.yaml" + - "plugins/frontends/ping_frontend.yaml" + +MICRO_SERVICES: + - "plugins/microservices/static_attributes.yaml" + +LOGGING: + version: 1 + formatters: + simple: + format: "[%(asctime)s] [%(levelname)s] [%(name)s.%(funcName)s] %(message)s" + handlers: + stdout: + class: logging.StreamHandler + stream: "ext://sys.stdout" + level: DEBUG + formatter: simple + syslog: + class: logging.handlers.SysLogHandler + address: "/dev/log" + level: DEBUG + formatter: simple + debug_file: + class: logging.FileHandler + filename: satosa-debug.log + encoding: utf8 + level: DEBUG + formatter: simple + error_file: + class: logging.FileHandler + filename: satosa-error.log + encoding: utf8 + level: ERROR + formatter: simple + info_file: + class: logging.handlers.RotatingFileHandler + filename: satosa-info.log + encoding: utf8 + maxBytes: 10485760 # 10MB + backupCount: 20 + level: INFO + formatter: simple + loggers: + satosa: + level: DEBUG + saml2: + level: DEBUG + oidcendpoint: + level: DEBUG + pyop: + level: DEBUG + oic: + level: DEBUG + root: + level: DEBUG + handlers: + - stdout diff --git a/roles/midproxy/tasks/main.yml b/roles/midproxy/tasks/main.yml new file mode 100644 index 000000000..eb7bb93a4 --- /dev/null +++ b/roles/midproxy/tasks/main.yml @@ -0,0 +1,56 @@ +--- +- name: Create directory to keep configfile + ansible.builtin.file: + dest: "/opt/openconext/midproxy" + state: directory + # owner: root + # group: root + # mode: "0770" + +- name: Copy EB SP metadata + ansible.builtin.copy: + src: "{{ inventory_dir }}/files/midproxy/{{ midproxy.sp_metadata }}" + dest: "/opt/openconext/midproxy/{{ midproxy.sp_metadata }}" + # owner: "root" + # group: root + # mode: "0740" + +- name: Copy SATOSA conf files + ansible.builtin.copy: + src: "{{ item }}" + dest: "/opt/openconext/midproxy/{{ item }}" + with_items: + - internal_attributes.yaml + - proxy_config.yaml + - plugins + +- name: Create the SATOSA container + community.docker.docker_container: + name: midproxy + image: satosa:{{ midproxy.satosa_version }} + pull: true + restart_policy: "always" + state: started + networks: + - name: "loadbalancer" + env: + SATOSA_BASE: 'https://midproxy.{{ openconextaccess_base_domain }}' + SATOSA_STATE_ENCRYPTION_KEY: '{{ midproxy.state_encryption_key }}' + SATOSA_ISSUER: '{{ midproxy.issuer }}' + SATOSA_CLIENT_ID: '{{ midproxy.client_id }}' + SATOSA_CLIENT_SECRET: '{{ midproxy.client_secret }}' + SATOSA_SP_METADATA: '{{ midproxy.sp_metadata }}' + # curl is not availavble in the minimized satosa image + # so this healthcheck won't work + # healthcheck: + # test: ["CMD", "curl", "--fail" , "http://localhost" ] + # interval: 10s + # timeout: 10s + # retries: 3 + # start_period: 10s + volumes: + - /opt/openconext/midproxy:/etc/satosa + labels: + traefik.http.routers.midproxy.rule: "Host(`midproxy.{{ openconextaccess_base_domain }}`)" + traefik.http.routers.midproxy.tls: "true" + traefik.enable: "true" diff --git a/roles/redis/defaults/main.yml b/roles/redis/defaults/main.yml new file mode 100644 index 000000000..d4eb4b182 --- /dev/null +++ b/roles/redis/defaults/main.yml @@ -0,0 +1,11 @@ +--- +redis: "{{ redis_defaults | combine(redis_overrides, recursive=true) }}" +redis_defaults: + image: "docker.io/library/redis:7" + conf_dir: "{{ current_release_appdir }}/redis" + data_dir: "{{ current_release_appdir }}/redis/data" + user: redis + group: redis + redis_user: default + redis_password: changethispassword + max_memory: 100mb diff --git a/roles/redis/handlers/main.yml b/roles/redis/handlers/main.yml new file mode 100644 index 000000000..5ed78e133 --- /dev/null +++ b/roles/redis/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Restart redis container + community.docker.docker_container: + name: redis + state: started + restart: true diff --git a/roles/redis/tasks/main.yml b/roles/redis/tasks/main.yml new file mode 100644 index 000000000..65e7392ef --- /dev/null +++ b/roles/redis/tasks/main.yml @@ -0,0 +1,61 @@ +--- +- name: "Create redis group" + group: + name: "{{ redis.group }}" + state: "present" + register: "result" + +- name: "Save redis group gid" + set_fact: + redis_group_gid: "{{ result.gid }}" + +- name: "Create redis user" + user: + name: "{{ redis.user }}" + group: "{{ redis.group }}" + comment: "User to run SRAM Redis service" + shell: "/bin/false" + password: "!" + home: "{{ redis.conf_dir }}" + create_home: false + state: "present" + register: "result" + +- name: "Save redis user uid" + set_fact: + redis_user_uid: "{{ result.uid }}" + +- name: "Create directories" + file: + path: "{{item.path}}" + state: "directory" + owner: "{{ redis.user }}" + group: "{{ redis.group }}" + mode: "{{item.mode}}" + with_items: + - { path: "{{redis.conf_dir}}", mode: "0755" } + - { path: "{{redis.data_dir}}", mode: "0755" } + +- name: "Create redis config" + template: + src: "redis.conf.j2" + dest: "{{ redis.conf_dir }}/redis.conf" + owner: "{{ redis.user }}" + group: "{{ redis.group }}" + mode: "0644" + notify: "Restart redis container" + +- name: "Create redis container" + community.docker.docker_container: + name: "redis" + image: "{{ redis.image }}" + restart_policy: "always" + state: "started" + user: "{{ redis_user_uid }}:{{ redis_group_gid }}" + command: | + redis-server /usr/local/etc/redis/redis.conf + volumes: + - "{{ redis.conf_dir }}:/usr/local/etc/redis" + - "{{ redis.data_dir }}:/data" + networks: + - name: loadbalancer diff --git a/roles/redis/templates/redis.conf.j2 b/roles/redis/templates/redis.conf.j2 new file mode 100644 index 000000000..ba231dc58 --- /dev/null +++ b/roles/redis/templates/redis.conf.j2 @@ -0,0 +1,3 @@ +user {{redis.redis_user}} on +@all ~* &* >{{redis.redis_password}} +maxmemory {{ redis.max_memory }} +maxmemory-policy allkeys-lru diff --git a/roles/redis/vars/main.yml b/roles/redis/vars/main.yml new file mode 100644 index 000000000..761942f7b --- /dev/null +++ b/roles/redis/vars/main.yml @@ -0,0 +1 @@ +current_release_appdir: /opt/openconext diff --git a/roles/sbs/defaults/main.yml b/roles/sbs/defaults/main.yml new file mode 100644 index 000000000..f21addf5f --- /dev/null +++ b/roles/sbs/defaults/main.yml @@ -0,0 +1,159 @@ +--- +sbs: "{{ sbs_defaults | combine(sbs_overrides, recursive=true) }}" +sbs_defaults: + openidc_timeout: 86400 + sram_conf_dir: "{{ current_release_appdir }}/sram" + + work_dir: "{{ sram_conf_dir }}/sbs" + git_dir: "{{ sbs.work_dir }}/sbs" + env_dir: "{{ sbs.work_dir }}/sbs-env" + conf_dir: "{{ sbs.work_dir }}/config" + log_dir: "{{ sbs.work_dir }}/log" + cert_dir: "{{ sbs.work_dir }}/cert" + apache_conf: "{{ sbs.work_dir }}/sbs.conf" + nginx_conf: "{{ sbs.work_dir }}/nginx.conf" + + + db_name: "sbs" + db_user: "sbsrw" + dbbackup_user: "sbs_backupper" + migration_user: "sbs_migrater" + + db_connection: "\ + mysql+mysqldb://%s:%s@{{ mariadb_host }}/{{ sbs_db_name }}\ + ?ssl=true&charset=utf8mb4" + db_connection_sbs: "{{ sbs_db_connection | format(sbs_db_user, sbs_db_password) }}" + db_connection_migration: "\ + {{ sbs_db_connection | format(sbs_migration_user, sbs_migration_password) }}" + + redis_host: redis + redis_port: 6379 + redis_ssl: false + redis_user: default + + mail_host: "{{ mail.relay_to }}" + mail_port: "{{ mail.relay_port }}" + + user: "sbs" + group: "sbs" + + session_lifetime: 1440 + secret_key_suffix: "" + + oidc_crypto_password: "CHANGEME" + uid_attribute: "sub" + + disclaimer_color: "#a29c13" + disclaimer_label: wsgi + + urn_namespace: "urn:example:sbs" + eppn_scope: "sbs.example.edu" + restricted_co_default_org: "example.org" + + mail_sender_name: "SURF" + mail_sender_email: "no-reply@localhost" + exceptions_mail: "root@localhost" + + support_email: "sram-support@localhost" + admin_email: "sram-beheer@localhost" + ticket_email: "sram-support@surf.nl" + eduteams_email: "eduteams@localhost" + + wiki_link: "https://www.example.org/wiki" + + backend_port: 8080 + num_workers: 2 + + cron_hour_of_day: 4 + seed_allowed: True + api_keys_enabled: True + feedback_enabled: True + audit_trail_notifications_enabled: True + send_exceptions: False + send_js_exceptions: False + second_factor_authentication_required: True + totp_token_name: "SRAM-example" + notifications_enabled: True + invitation_reminders_enabled: True + invitation_expirations_enabled: True + open_requests_enabled: True + scim_sweep: False + impersonation_allowed: True + admin_platform_backdoor_totp: True + past_dates_allowed: True + mock_scim_enabled: True + log_to_stdout: True + + delete_orphaned: True + suspension_inactive_days: 365 + suspension_reminder_days: 14 + suspension_notify_admin: False + + oidc_config_url: "http://localhost/.well-known/openid-configuration" + oidc_authz_endpoint: "http://localhost/OIDC/authorization" + oidc_token_endpoint: "http://localhost/OIDC/token" + oidc_userinfo_endpoint: "http://localhost/OIDC/userinfo" + oidc_jwks_endpoint: "http://localhost/OIDC/jwks.json" + oidc_redirect_uri: "https://sbs.scz-vm.net/api/users/resume-session" + mfa_idp_allowed: false + eduteams_continue_endpoint: "https://localhost/continue" + eb_continue_endpoint: "https://engine.(.*)surfconext.nl(.*)" + oidc_jwt_audience: "https://localhost" + continue_eduteams_redirect_uri: "https://localhost/continue" + oidc_verify_peer: False + oidc_scopes: + - openid + + manage_base_enabled: False + manage_base_url: "https://manage.test2.surfconext.nl" + manage_sram_rp_entity_id: "sbs.test2.sram.surf.nl" + manage_verify_peer: False + + idp_metadata_url: "https://metadata.surfconext.nl/signed/2023/edugain-downstream-idp.xml " + backup_dir: "{{backup_base}}/sbs" + + swagger_enabled: true + + ssid_identity_providers: [] + surf_secure_id: + environment: "unknown.example.org" + sp_entity_id: "https://sbs.{{base_domain}}" + acs_url: "https://{{base_domain}}/api/users/acs" + sa_gw_environment: "sa-gw.unknown.example.org" + sa_idp_certificate: | + -----BEGIN CERTIFICATE----- + 12345 + -----END CERTIFICATE----- + priv: | + -----BEGIN RSA PRIVATE KEY----- + abcde + -----END RSA PRIVATE KEY----- + pub: | + -----BEGIN CERTIFICATE----- + 12345 + -----END CERTIFICATE----- + + ssid_authncontext: "\ + http://{{ sbs.surf_secure_id.environment }}/assurance/sfo-level2" + ssid_entityid: "\ + https://{{ sbs.surf_secure_id.sa_gw_environment }}/second-factor-only/metadata" + ssid_sso_endpoint: "\ + https://{{ sbs.surf_secure_id.sa_gw_environment }}/second-factor-only/single-sign-on" + + mfa_sso_minutes: 10 + mfa_fallback_enabled: true + + ldap_url: "ldap://ldap.example.com/dc=example,dc=com" + ldap_bind_account: "cn=admin,dc=entity_id,dc=services,dc=sram-tst,dc=surf,dc=nl" + + csp_style_hashes: + - 'sha256-0+ANsgYUJdh56RK8gGvTF2vnriYqvFHfWqtA8xXa+bA=' + - 'sha256-3SnfHQolDHbZMbDAPmhrZf1keHiXfj/KJyh2phhFAAY=' + - 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' + - 'sha256-Ng6y+QCkPChG4Q49SIfXB5ToIDcDhITtQNFkDBPpCTw=' + - 'sha256-orBPipbqpMvkNi+Z+m6qEn0XS6ymmAQE6+FwCNs1FbQ=' + - 'sha256-vFt3L2qLqpJmRpcXGbYr2UVSmgSp9VCUzz2lnqWIATw=' + - 'sha256-SU3XCwbQ/8qgzoGOWCYdkwIr3xRrl5rsvdFcpw8NSiE=' # on /new-service-request + - 'sha256-WTC9gHKjIpzl5ub1eg/YrRy/k+jlzeyRojah9dxAApc=' # on /new-service-request + + engine_block_api_token: secret diff --git a/roles/sbs/files/yarn.gpg b/roles/sbs/files/yarn.gpg new file mode 100644 index 000000000..3e9e7d155 --- /dev/null +++ b/roles/sbs/files/yarn.gpg @@ -0,0 +1,243 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +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 +=oMDY +-----END PGP PUBLIC KEY BLOCK----- diff --git a/roles/sbs/handlers/main.yml b/roles/sbs/handlers/main.yml new file mode 100644 index 000000000..012cba535 --- /dev/null +++ b/roles/sbs/handlers/main.yml @@ -0,0 +1,9 @@ +--- +- name: Restart sbs containers + community.docker.docker_container: + name: "{{ item }}" + state: started + restart: true + loop: + - sbs + - sbs_server diff --git a/roles/sbs/tasks/main.yml b/roles/sbs/tasks/main.yml new file mode 100644 index 000000000..8a52adc4e --- /dev/null +++ b/roles/sbs/tasks/main.yml @@ -0,0 +1,171 @@ +--- +- name: "Create SBS group" + group: + name: "{{ sbs.group }}" + state: "present" + register: "result" + +- name: "Save SBS group gid" + set_fact: + sbs_group_gid: "{{ result.gid }}" + +- name: "Create SBS user" + user: + name: "{{ sbs.user }}" + group: "{{ sbs.group }}" + comment: "User to run SBS service" + shell: "/bin/false" + password: "!" + home: "{{ sbs.conf_dir }}" + create_home: false + state: "present" + register: "result" + +- name: "Save sbs user uid" + set_fact: + sbs_user_uid: "{{ result.uid }}" + +- name: "Create directories" + file: + path: "{{item.path}}" + state: "directory" + owner: "root" + group: "{{sbs_group_gid}}" + mode: "{{item.mode}}" + with_items: + - { path: "{{sbs.work_dir}}", mode: "0755" } + - { path: "{{sbs.conf_dir}}", mode: "0755" } + - { path: "{{sbs.conf_dir}}/saml", mode: "0755" } + - { path: "{{sbs.log_dir}}", mode: "0775" } + - { path: "{{sbs.cert_dir}}", mode: "0755" } + +- name: "Fix file permissions" + file: + path: "{{sbs.log_dir}}/{{item}}" + owner: "root" + group: "{{sbs_group_gid}}" + mode: "0664" + state: "touch" + modification_time: "preserve" + access_time: "preserve" + with_items: + - "sbs.log" + - "sbs_debug.log" + +- name: "Copy wildcard backend cert" + copy: + content: "{{wildcard_backend_cert.pub}}" + dest: "{{sbs.cert_dir}}/backend.crt" + owner: "root" + group: "root" + mode: "0644" + notify: "Restart sbs containers" + +- name: "Copy https cert" + copy: + content: "{{https_cert.cert}}" + dest: "{{sbs.cert_dir}}/frontend.crt" + owner: "root" + group: "root" + mode: "0644" + notify: "Restart sbs containers" + +- name: "Install database certificate" + copy: + dest: "{{sbs.db_cert_path}}" + content: "{{ sbs.db_tls_cert }}" + owner: "root" + group: "root" + mode: "0644" + +- name: "Create SBS config files" + template: + src: "{{item.name}}.j2" + dest: "{{ sbs.conf_dir }}/{{item.name}}" + owner: "root" + group: "{{sbs_group_gid}}" + mode: "{{item.mode}}" + with_items: + - { name: "config.yml", mode: "0644" } + - { name: "alembic.ini", mode: "0644" } + - { name: "disclaimer.css", mode: "0644" } + - { name: "sbs-apache.conf", mode: "0644" } + no_log: "{{sram_ansible_nolog}}" + notify: "Restart sbs containers" + +- name: "Run SBS migrations" + throttle: 1 + community.docker.docker_container: + name: "sbs_migration" + image: "{{ sbs.server_image }}" + pull: "never" + state: "started" + restart_policy: "no" + detach: false + env: + RUNAS_UID: "{{ sbs_user_uid | string }}" + RUNAS_GID: "{{ sbs_group_gid | string }}" + CONFIG: "/opt/sbs/server/config/config.yml" + MIGRATIONS_ONLY: "1" + # don't actually run the server + command: "/bin/true" + volumes: + - "{{ sbs.conf_dir }}:/sbs-config" + - "{{ sbs.cert_dir }}:/sbs-config/cert" + - "{{ sbs.log_dir }}:/opt/sbs/log" + networks: + - name: "{{internal_network}}" + register: "result" + failed_when: "'container' not in result or result.container.State.ExitCode != 0" + changed_when: "'[alembic.runtime.migration] Running upgrade' in result.container.Output" + notify: "Restart sbs containers" + +# Remove the migration container; we can't do that with auto_remove, because if we use that, ansible +# will not save the output in result +- name: "Remove migration container" + community.docker.docker_container: + name: "sbs_migration" + state: "absent" + # TODO: fix this by only running this if "sbs_image is changed" + changed_when: false + +- name: "Start sbs container" + community.docker.docker_container: + name: "sbs" + image: "{{ sbs.image }}" + pull: "never" + restart_policy: "always" + state: "started" + env: + RUN_MIGRATIONS: "0" + volumes: + - "{{ sbs.conf_dir }}/sbs-apache.conf:/etc/apache2/sites-enabled/sbs.conf:ro" + networks: + - name: loadbalancer + labels: + traefik.enable: "true" + traefik.docker.network: "{{traefik_network}}" + traefik.http.routers.sbs.rule: "Host(`{{ sbs.base_domain }}`)" + traefik.http.routers.sbs.tls: "true" + +- name: "Start SBS server container" + community.docker.docker_container: + name: "sbs_server" + image: "{{ sbs.server_image }}" + restart_policy: "always" + state: "started" + env: + RUNAS_UID: "{{ sbs_user_uid | string }}" + RUNAS_GID: "{{ sbs_group_gid | string }}" + CONFIG: "/opt/sbs/server/config/config.yml" + REQUESTS_CA_BUNDLE: "/etc/ssl/certs/ca-certificates.crt" + RUN_MIGRATIONS: "0" + pull: "always" + command: "/usr/local/bin/gunicorn --preload --worker-class eventlet --workers 8 --bind 0.0.0.0:8080 server.__main__:app" + volumes: + - "{{ sbs.conf_dir }}:/sbs-config" + - "{{ sbs.cert_dir }}:/sbs-config/cert" + - "{{ sbs.log_dir }}:/opt/sbs/log" + - "/tmp/ci-runner:/tmp/ci-runner" + networks: + - name: loadbalander diff --git a/roles/sbs/templates/alembic.ini.j2 b/roles/sbs/templates/alembic.ini.j2 new file mode 100644 index 000000000..7849e4f89 --- /dev/null +++ b/roles/sbs/templates/alembic.ini.j2 @@ -0,0 +1,72 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = migrations + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# timezone to use when rendering the date +# within the migration file as well as the filename. +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +#truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; this defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path +# version_locations = %(here)s/bar %(here)s/bat alembic/versions + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = {{ sbs_db_connection_migration }} + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = NOTSET +handlers = console + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = DEBUG +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/roles/sbs/templates/config.yml.j2 b/roles/sbs/templates/config.yml.j2 new file mode 100644 index 000000000..11f029af7 --- /dev/null +++ b/roles/sbs/templates/config.yml.j2 @@ -0,0 +1,264 @@ +--- +database: + uri: {{ sbs.db_connection_sbs }} + +redis: +{% if environment_name == 'tst2' %} + uri: "redis://{{redis_vhost}}/" +{% else %} + uri: "redis{% if sbs.redis_ssl %}s{% endif %}://{{ sbs.redis_user }}:{{ sbs.redis_password }}@{{ sbs.redis_host }}:{{ sbs.redis_port }}/" +{% endif %} + +# add a per-release suffix here to invalidate sessions on new releases +secret_key: {{ sbs.db_secret }}{{sbs.secret_key_suffix}} +# Must be a base64 encoded key of 128, 192, or 256 bits. +# Generate: base64.b64encode(os.urandom(256 // 8)).decode() +encryption_key: {{ sbs.encryption_key }} + +# Lifetime of session in minutes (one day is 60 * 24) +permanent_session_lifetime: {{ sbs.session_lifetime }} + +logging: + log_to_stdout: {{ sbs.log_to_stdout }} + +# Valid scopes are "READ" and "WRITE" +api_users: +{% for name, user in sbs.api_users.items() %} + - name: "{{ name }}" + password: "{{ user.password }}" + scopes: "[ {{ user.scopes | join(', ') }} ]" +{% endfor %} + +oidc: + client_id: "{{ sbs.client_id }}" + client_secret: "{{ sbs.client_secret }}" + audience: "{{ sbs.oidc_jwt_audience }}" + verify_peer: {{ sbs.oidc_verify_peer }} + authorization_endpoint: "{{ sbs.oidc_authz_endpoint}}" + token_endpoint: "{{ sbs.oidc_token_endpoint }}" + userinfo_endpoint: "{{ sbs.oidc_userinfo_endpoint }}" + jwks_endpoint: "{{ sbs.oidc_jwks_endpoint }}" + #Note that the paths for these uri's is hardcoded and only domain and port differ per environment + redirect_uri: "{{ sbs.oidc_redirect_uri }}" + continue_eduteams_redirect_uri: "{{ sbs.eduteams_continue_endpoint }}" + continue_eb_redirect_uri: "{{ sbs.eb_continue_endpoint }}" + second_factor_authentication_required: {{ sbs.second_factor_authentication_required }} + totp_token_name: "{{ sbs.totp_token_name }}" + # The service_id in the proxy_authz endpoint when logging into SBS. Most likely to equal the oidc.client_id + sram_service_entity_id: "{{ sbs.client_id }}" + scopes: {{ sbs.oidc_scopes }} + +base_scope: "{{ base_domain }}" +entitlement_group_namespace: "{{ sbs.urn_namespace }}" +eppn_scope: " {{ sbs.eppn_scope }}" +scim_schema_sram: "urn:mace:surf.nl:sram:scim:extension" +collaboration_creation_allowed_entitlement: "urn:mace:surf.nl:sram:allow-create-co" + +{% if environment_name == "prd" %} +environment_disclaimer: "" +{% else %} +environment_disclaimer: "{{ sbs.disclaimer_label }}" +{% endif %} + +# All services in the white list can be requested in the create-restricted-co API +# The default organisation is a fallback for when the administrator has no schac_home_org +restricted_co: + services_white_list: [ "https://cloud" ] + default_organisation: "{{ sbs.restricted_co_default_org }}" + +mail: + host: {{ sbs.mail_host }} + port: {{ sbs.mail_port }} + sender_name: {{ sbs.mail_sender_name }} + sender_email: {{ sbs.mail_sender_email }} + suppress_sending_mails: False + info_email: {{ sbs.support_email }} + beheer_email: {{ sbs.admin_email }} + ticket_email: {{ sbs.ticket_email }} + eduteams_email: {{ sbs.eduteams_email }} + # Do we mail a summary of new Organizations and Services to the beheer_email? + audit_trail_notifications_enabled: {{ sbs.audit_trail_notifications_enabled }} + account_deletion_notifications_enabled: True + send_exceptions: {{ sbs.send_exceptions }} + send_js_exceptions: {{ sbs.send_js_exceptions }} + send_exceptions_recipients: [ "{{ sbs.exceptions_mail }}" ] + environment: "{{ base_domain }}" + +manage: + enabled: {{ sbs.manage_base_enabled }} + # The entity_id of the SRAM RP in Manage for API retrieval, e.g "sbs.test2.sram.surf.nl" + sram_rp_entity_id: "{{ sbs.manage_sram_rp_entity_id }}" + base_url: "{{ sbs.manage_base_url }}" + user: "{{ sbs.manage_user }}" + password: "{{ sbs.manage_password }}" + verify_peer: {{ sbs.manage_verify_peer }} + +aup: + version: 1 + url_aup_en: "https://edu.nl/6wb63" + url_aup_nl: "https://edu.nl/6wb63" + +base_url: {{ sbs.base_url }} +socket_url: {{ sbs.base_url }} +base_server_url: {{ sbs.base_url }} +wiki_link: {{ sbs.wiki_link }} + +admin_users: +{% for admin_user in sbs.admin_users %} + - uid: "{{ admin_user.uid }}" +{% endfor %} + +organisation_categories: + - "HBO" + - "MBO" + - "UMC" + - "University" + - "Research" + - "SURF" + +feature: + seed_allowed: {{ sbs.seed_allowed }} + api_keys_enabled: {{ sbs.api_keys_enabled }} + feedback_enabled: {{ sbs.feedback_enabled }} + impersonation_allowed: {{ sbs.impersonation_allowed }} + sbs_swagger_enabled: {{ sbs.swagger_enabled }} + admin_platform_backdoor_totp: {{ sbs.admin_platform_backdoor_totp }} + past_dates_allowed: {{ sbs.past_dates_allowed }} + mock_scim_enabled: {{ sbs.mock_scim_enabled }} + +metadata: + idp_url: "{{sbs.idp_metadata_url}}" + parse_at_startup: True + # No need for environment specific values + scope_override: + knaw.nl: "Koninklijke Nederlandse Akademie van Wetenschappen (KNAW)" + +platform_admin_notifications: + # Do we daily check for CO join_requests and CO requests and send a summary mail to beheer_email? + enabled: False + cron_hour_of_day: {{ sbs.cron_hour_of_day }} + # How long before we include open join_requests in the summary + outstanding_join_request_days_threshold: 7 + # How long before we include open CO requests in the summary + outstanding_coll_request_days_threshold: 7 + +user_requests_retention: + # Do we daily check for CO join_requests and CO requests and delete approved and denied? + enabled: {{ sbs.notifications_enabled }} + cron_hour_of_day: {{ sbs.cron_hour_of_day }} + # How long before we delete approved / denied join_requests + outstanding_join_request_days_threshold: 90 + # How long before we delete approved / denied CO requests + outstanding_coll_request_days_threshold: 90 + +# The retention config determines how long users may be inactive, how long the reminder email is valid and when do we resent the magic link +retention: + cron_hour_of_day: {{ sbs.cron_hour_of_day }} + # how many days of inactivity before a user is suspended + # 0 allows for any last_login_date in the past to trigger suspension notification + allowed_inactive_period_days: {{ sbs.suspension_inactive_days }} + # how many days before suspension do we send a warning + # -1 will suspend notified users on second suspension cron + reminder_suspend_period_days: {{ sbs.suspension_reminder_days }} + # how many days after suspension do we delete the account + remove_suspended_users_period_days: 90 + # how many days before deletion do we send a reminder + reminder_expiry_period_days: 7 + # whether to send a notification of the result of the retention process to the beheer_email + admin_notification_mail: {{ sbs.suspension_notify_admin }} + +collaboration_expiration: + # Do we daily check for CO's that will be deleted because they have been expired? + enabled: {{ sbs.notifications_enabled }} + cron_hour_of_day: {{ sbs.cron_hour_of_day }} + # How long after expiration do we actually delete expired collaborations + expired_collaborations_days_threshold: 90 + # How many days before actual expiration do we mail the organisation members + expired_warning_mail_days_threshold: 10 + +collaboration_suspension: + # Do we daily check for CO's that will be suspended because of inactivity? + enabled: {{ sbs.notifications_enabled }} + cron_hour_of_day: {{ sbs.cron_hour_of_day }} + # After how many days of inactivity do we suspend collaborations + collaboration_inactivity_days_threshold: 365 + # How many days before actual suspension do we mail the organisation members + inactivity_warning_mail_days_threshold: 10 + # After how many days after suspension do we actually delete the collaboration + collaboration_deletion_days_threshold: 90 + +membership_expiration: + # Do we daily check for memberships that will be deleted because they have been expired? + enabled: {{ sbs.notifications_enabled }} + cron_hour_of_day: {{ sbs.cron_hour_of_day }} + # How long after expiration do we actually delete expired memberships + expired_memberships_days_threshold: 90 + # How many days before actual expiration do we mail the co admin and member + expired_warning_mail_days_threshold: 10 + +invitation_reminders: + # Do we daily check for invitations that need a reminder? + enabled: {{ sbs.invitation_reminders_enabled }} + cron_hour_of_day: {{ sbs.cron_hour_of_day }} + # How many days before expiration of an invitation do we remind the user? + invitation_reminders_threshold: 5 + +invitation_expirations: + # Do we daily check for invitations that are expired / accepted and are eligible for deletion ? + enabled: {{ sbs.invitation_expirations_enabled }} + cron_hour_of_day: {{ sbs.cron_hour_of_day }} + # How long after expiration of an invitation do we delete the invitation? + nbr_days_remove_expired_invitations: 10 + # How long after expiration of an API created invitation do we delete the invitation? + nbr_days_remove_api_expired_invitations: 30 + +orphan_users: + # Do we daily check for users that are orphans soo they can be deleted? + enabled: {{ sbs.delete_orphaned }} + cron_hour_of_day: {{ sbs.cron_hour_of_day }} + # How long after created do we delete orphan users + delete_days_threshold: 14 + +open_requests: + # Do we weekly check for all open requests? + enabled: {{ sbs.open_requests_enabled }} + cron_day_of_week: 1 + +scim_sweep: + # Do we enable scim sweeps? + enabled: {{ sbs.scim_sweep }} + # How often do we check if scim sweeps are needed per service + cron_minutes_expression: "*/15" + +ldap: + url: "{{ sbs.ldap_url }}" + bind_account: "{{ sbs.ldap_bind_account }}" + +# A MFA login in a different flow is valid for X minutes +mfa_sso_time_in_minutes: {{sbs.mfa_sso_minutes}} + +# whether to fall back to TOTP MFA +mfa_fallback_enabled: {{sbs.mfa_fallback_enabled}} + +# Lower case entity ID's and schac_home allowed skipping MFA. +# Note that for a login directly into SRAM only schac_home can be used as the entity_idp of the IdP is unknown +mfa_idp_allowed: {{sbs.mfa_idp_allowed}} + +# Lower case schachome organisations / entity ID's where SURFSecure ID is used for step-up +ssid_identity_providers: {{sbs.ssid_identity_providers}} + +ssid_config_folder: saml + +pam_web_sso: + session_timeout_seconds: 300 + +rate_limit_totp_guesses_per_30_seconds: 10 + +# The uid's of user that will never be suspended or deleted +excluded_user_accounts: +{% for excluded_user in sbs.excluded_users %} + - uid: "{{ excluded_user.uid }}" +{% endfor %} + +engine_block: + api_token: {{ sbs.engine_block_api_token }} diff --git a/roles/sbs/templates/disclaimer.css.j2 b/roles/sbs/templates/disclaimer.css.j2 new file mode 100644 index 000000000..e89bbfce7 --- /dev/null +++ b/roles/sbs/templates/disclaimer.css.j2 @@ -0,0 +1,6 @@ +{% if environment_name!="prd" -%} +body::after { + background: {{ sbs_disclaimer_color }}; + content: "{{ sbs_disclaimer_label }}"; +} +{% endif %} diff --git a/roles/sbs/templates/saml_advanced_settings.json.j2 b/roles/sbs/templates/saml_advanced_settings.json.j2 new file mode 100644 index 000000000..bdde32050 --- /dev/null +++ b/roles/sbs/templates/saml_advanced_settings.json.j2 @@ -0,0 +1,35 @@ +{ + "security": { + "nameIdEncrypted": false, + "authnRequestsSigned": true, + "logoutRequestSigned": false, + "logoutResponseSigned": false, + "signMetadata": false, + "wantMessagesSigned": false, + "wantAssertionsSigned": true, + "wantNameId" : true, + "wantNameIdEncrypted": false, + "wantAttributeStatement": false, + "wantAssertionsEncrypted": false, + "requestedAuthnContext": ["{{sbs_ssid_authncontext}}"], + "requestedAuthnContextComparison": "minimum", + "failOnAuthnContextMismatch": false, + "allowSingleLabelDomains": false, + "signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", + "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256", + "rejectDeprecatedAlgorithm": true + }, + "contactPerson": { + "technical": { + "givenName": "{{ mail.admin_name }}", + "emailAddress": "{{ mail.admin_address }}" + } + }, + "organization": { + "en-US": { + "name": "{{ org.name }}", + "displayname": "{{ org.name }}", + "url": "{{ org.url }}" + } + } +} diff --git a/roles/sbs/templates/saml_settings.json.j2 b/roles/sbs/templates/saml_settings.json.j2 new file mode 100644 index 000000000..bb5788e97 --- /dev/null +++ b/roles/sbs/templates/saml_settings.json.j2 @@ -0,0 +1,22 @@ +{ + "strict": true, + "debug": true, + "sp": { + "entityId": "{{ sbs_surf_secure_id.sp_entity_id }}", + "assertionConsumerService": { + "url": "{{ sbs_surf_secure_id.acs_url }}", + "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + }, + "NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", + "x509cert": "{{ sbs_surf_secure_id.pub | barepem }}", + "privateKey": "{{ sbs_surf_secure_id.priv | barepem }}" + }, + "idp": { + "entityId": "{{ sbs_ssid_entityid }}", + "singleSignOnService": { + "url": "{{ sbs_ssid_sso_endpoint }}", + "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + }, + "x509cert": "{{ sbs_surf_secure_id.sa_idp_certificate | barepem }}" + } +} diff --git a/roles/sbs/templates/sbs-apache.conf.j2 b/roles/sbs/templates/sbs-apache.conf.j2 new file mode 100644 index 000000000..13752ee56 --- /dev/null +++ b/roles/sbs/templates/sbs-apache.conf.j2 @@ -0,0 +1,30 @@ +ServerName {{ hostnames.sbs }} +#ErrorLog /proc/self/fd/2 +#CustomLog /proc/self/fd/1 common +DocumentRoot /opt/sbs/client/build + +Header set Content-Security-Policy "default-src 'self'; base-uri 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-src 'none'; form-action 'self' https://*.{{ base_domain }}; frame-ancestors 'none'; block-all-mixed-content;" +Header set Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=(), clipboard-read=(), clipboard-write=(self), gamepad=(), speaker-selection=()" + +RewriteEngine On +RewriteCond %{REQUEST_URI} !^/(api|pam-weblogin|flasgger_static|swagger|health|config|info|socket.io) +RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f +RewriteRule ^/(.*)$ /index.html [L] + +ProxyRequests off +ProxyPassMatch ^/(api|pam-weblogin|flasgger_static|swagger|health|config|info) http://{{ containers.sbs_server }}:{{sbs_backend_port}}/ +ProxyPassReverse / http://{{ containers.sbs_server }}:{{sbs_backend_port}}/ +ProxyPass /socket.io/ ws://{{ containers.sbs_server }}:{{sbs_backend_port}}/socket.io/ +ProxyPassReverse /socket.io/ ws://{{ containers.sbs_server }}:{{sbs_backend_port}}/socket.io/ + + + Header set Cache-Control: "public, max-age=31536000, immutable" + + + Header set Cache-Control: "no-cache, private" + + + + Require all granted + Options -Indexes + diff --git a/roles/sbs/templates/sbs.service.j2 b/roles/sbs/templates/sbs.service.j2 new file mode 100644 index 000000000..2920ddc8d --- /dev/null +++ b/roles/sbs/templates/sbs.service.j2 @@ -0,0 +1,32 @@ +[Unit] +Description=SBS +After=network.target + +[Service] +DynamicUser=true +User=_sram_sbs +Group=_sram_sbs +SupplementaryGroups={{sbs_group}} + +WorkingDirectory={{sbs_git_dir}} +ReadWritePaths={{sbs_log_dir}} +NoNewPrivileges=true +PrivateTmp=true + +Environment="CONFIG=config/config.yml" +Environment="PROFILE=log_to_stdout" +# the python requests module uses the CAs provided by the certifi package by default +# we'll just take the OS-provided CAs, thankyouverymuch +Environment="REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt" + +Type=notify +ExecStart={{sbs_env_dir}}/bin/gunicorn --worker-class eventlet --workers {{sbs_num_workers}} --bind 127.0.0.1:8080 server.__main__:app + +Restart=on-failure +RestartSec=10 + +KillMode=mixed +TimeoutStopSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/roles/sbs/vars/main.yml b/roles/sbs/vars/main.yml new file mode 100644 index 000000000..761942f7b --- /dev/null +++ b/roles/sbs/vars/main.yml @@ -0,0 +1 @@ +current_release_appdir: /opt/openconext From 4be1e53eb9b7907b7c164c268a929fb0b30ee5a6 Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Thu, 11 Dec 2025 13:46:02 +0100 Subject: [PATCH 02/16] Better secrets --- roles/midproxy/tasks/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/roles/midproxy/tasks/main.yml b/roles/midproxy/tasks/main.yml index eb7bb93a4..133069e67 100644 --- a/roles/midproxy/tasks/main.yml +++ b/roles/midproxy/tasks/main.yml @@ -36,10 +36,10 @@ env: SATOSA_BASE: 'https://midproxy.{{ openconextaccess_base_domain }}' SATOSA_STATE_ENCRYPTION_KEY: '{{ midproxy.state_encryption_key }}' - SATOSA_ISSUER: '{{ midproxy.issuer }}' - SATOSA_CLIENT_ID: '{{ midproxy.client_id }}' - SATOSA_CLIENT_SECRET: '{{ midproxy.client_secret }}' SATOSA_SP_METADATA: '{{ midproxy.sp_metadata }}' + SATOSA_ISSUER: '{{ midproxy.issuer }}' + SATOSA_CLIENT_ID: '{{ midproxy_client_id }}' + SATOSA_CLIENT_SECRET: '{{ midproxy_client_secret }}' # curl is not availavble in the minimized satosa image # so this healthcheck won't work # healthcheck: From cdeb2f0e842d7c037c0a73d7dfc1a58938866312 Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Thu, 11 Dec 2025 13:52:29 +0100 Subject: [PATCH 03/16] Use /opt/sram config location --- roles/midproxy/tasks/main.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/roles/midproxy/tasks/main.yml b/roles/midproxy/tasks/main.yml index 133069e67..5a7b8c0db 100644 --- a/roles/midproxy/tasks/main.yml +++ b/roles/midproxy/tasks/main.yml @@ -1,7 +1,7 @@ --- - name: Create directory to keep configfile ansible.builtin.file: - dest: "/opt/openconext/midproxy" + dest: "/opt/sram/midproxy" state: directory # owner: root # group: root @@ -10,7 +10,7 @@ - name: Copy EB SP metadata ansible.builtin.copy: src: "{{ inventory_dir }}/files/midproxy/{{ midproxy.sp_metadata }}" - dest: "/opt/openconext/midproxy/{{ midproxy.sp_metadata }}" + dest: "/opt/sram/midproxy/{{ midproxy.sp_metadata }}" # owner: "root" # group: root # mode: "0740" @@ -18,10 +18,10 @@ - name: Copy SATOSA conf files ansible.builtin.copy: src: "{{ item }}" - dest: "/opt/openconext/midproxy/{{ item }}" + dest: "/opt/sram/midproxy/{{ item }}" with_items: - internal_attributes.yaml - - proxy_config.yaml + - proxy_conf.yaml - plugins - name: Create the SATOSA container @@ -49,7 +49,7 @@ # retries: 3 # start_period: 10s volumes: - - /opt/openconext/midproxy:/etc/satosa + - /opt/sram/midproxy:/etc/satosa labels: traefik.http.routers.midproxy.rule: "Host(`midproxy.{{ openconextaccess_base_domain }}`)" traefik.http.routers.midproxy.tls: "true" From 4ff5ca05212acdc156c1cc7bbb42b2c6db45b562 Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Thu, 11 Dec 2025 14:03:43 +0100 Subject: [PATCH 04/16] Chown to satosa user --- roles/midproxy/tasks/main.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/roles/midproxy/tasks/main.yml b/roles/midproxy/tasks/main.yml index 5a7b8c0db..4969e56bb 100644 --- a/roles/midproxy/tasks/main.yml +++ b/roles/midproxy/tasks/main.yml @@ -3,22 +3,24 @@ ansible.builtin.file: dest: "/opt/sram/midproxy" state: directory - # owner: root - # group: root - # mode: "0770" + owner: 1000 + group: 1000 + mode: "0770" - name: Copy EB SP metadata ansible.builtin.copy: src: "{{ inventory_dir }}/files/midproxy/{{ midproxy.sp_metadata }}" dest: "/opt/sram/midproxy/{{ midproxy.sp_metadata }}" - # owner: "root" - # group: root - # mode: "0740" + owner: 1000 + group: 1000 + mode: "0740" - name: Copy SATOSA conf files ansible.builtin.copy: src: "{{ item }}" dest: "/opt/sram/midproxy/{{ item }}" + owner: 1000 + group: 1000 with_items: - internal_attributes.yaml - proxy_conf.yaml @@ -35,7 +37,7 @@ - name: "loadbalancer" env: SATOSA_BASE: 'https://midproxy.{{ openconextaccess_base_domain }}' - SATOSA_STATE_ENCRYPTION_KEY: '{{ midproxy.state_encryption_key }}' + SATOSA_STATE_ENCRYPTION_KEY: '{{ midproxy_state_encryption_key }}' SATOSA_SP_METADATA: '{{ midproxy.sp_metadata }}' SATOSA_ISSUER: '{{ midproxy.issuer }}' SATOSA_CLIENT_ID: '{{ midproxy_client_id }}' From 48031f4872736663cb11fe0185b88743a3868e86 Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Thu, 11 Dec 2025 14:23:08 +0100 Subject: [PATCH 05/16] Fix recursive directory copy --- roles/midproxy/tasks/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/midproxy/tasks/main.yml b/roles/midproxy/tasks/main.yml index 4969e56bb..69e072ce4 100644 --- a/roles/midproxy/tasks/main.yml +++ b/roles/midproxy/tasks/main.yml @@ -24,7 +24,7 @@ with_items: - internal_attributes.yaml - proxy_conf.yaml - - plugins + - plugins/ - name: Create the SATOSA container community.docker.docker_container: From 14ba5df7873cdb519530584dea726a046fc69ae1 Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Thu, 11 Dec 2025 14:42:09 +0100 Subject: [PATCH 06/16] WIP --- roles/midproxy/tasks/main.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/roles/midproxy/tasks/main.yml b/roles/midproxy/tasks/main.yml index 69e072ce4..346f56e6e 100644 --- a/roles/midproxy/tasks/main.yml +++ b/roles/midproxy/tasks/main.yml @@ -42,6 +42,12 @@ SATOSA_ISSUER: '{{ midproxy.issuer }}' SATOSA_CLIENT_ID: '{{ midproxy_client_id }}' SATOSA_CLIENT_SECRET: '{{ midproxy_client_secret }}' + volumes: + - /opt/sram/midproxy:/etc/satosa + labels: + traefik.http.routers.midproxy.rule: "Host(`midproxy.{{ openconextaccess_base_domain }}`)" + traefik.http.routers.midproxy.tls: "true" + traefik.enable: "true" # curl is not availavble in the minimized satosa image # so this healthcheck won't work # healthcheck: @@ -50,9 +56,3 @@ # timeout: 10s # retries: 3 # start_period: 10s - volumes: - - /opt/sram/midproxy:/etc/satosa - labels: - traefik.http.routers.midproxy.rule: "Host(`midproxy.{{ openconextaccess_base_domain }}`)" - traefik.http.routers.midproxy.tls: "true" - traefik.enable: "true" From d2c46846326e907bee6c3869e100e8a73cd5924e Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Fri, 12 Dec 2025 11:49:27 +0100 Subject: [PATCH 07/16] loglevel INFO --- roles/midproxy/files/proxy_conf.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/roles/midproxy/files/proxy_conf.yaml b/roles/midproxy/files/proxy_conf.yaml index 550f56217..49594d6aa 100644 --- a/roles/midproxy/files/proxy_conf.yaml +++ b/roles/midproxy/files/proxy_conf.yaml @@ -29,18 +29,18 @@ LOGGING: stdout: class: logging.StreamHandler stream: "ext://sys.stdout" - level: DEBUG + level: INFO formatter: simple syslog: class: logging.handlers.SysLogHandler address: "/dev/log" - level: DEBUG + level: INFO formatter: simple debug_file: class: logging.FileHandler filename: satosa-debug.log encoding: utf8 - level: DEBUG + level: INFO formatter: simple error_file: class: logging.FileHandler @@ -58,16 +58,16 @@ LOGGING: formatter: simple loggers: satosa: - level: DEBUG + level: INFO saml2: - level: DEBUG + level: INFO oidcendpoint: - level: DEBUG + level: INFO pyop: - level: DEBUG + level: INFO oic: - level: DEBUG + level: INFO root: - level: DEBUG + level: INFO handlers: - stdout From fb384aa78aa925bb4d732372fa394ad51c1a6a37 Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Tue, 6 Jan 2026 11:08:30 +0100 Subject: [PATCH 08/16] Remove custom static SHO --- roles/midproxy/files/proxy_conf.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roles/midproxy/files/proxy_conf.yaml b/roles/midproxy/files/proxy_conf.yaml index 49594d6aa..8af0a3ec7 100644 --- a/roles/midproxy/files/proxy_conf.yaml +++ b/roles/midproxy/files/proxy_conf.yaml @@ -17,8 +17,8 @@ FRONTEND_MODULES: - "plugins/frontends/saml2_frontend.yaml" - "plugins/frontends/ping_frontend.yaml" -MICRO_SERVICES: - - "plugins/microservices/static_attributes.yaml" +# MICRO_SERVICES: +# - "plugins/microservices/static_attributes.yaml" LOGGING: version: 1 From c37af25f5d76cf50b3dfba290122e7f08347acfb Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Wed, 7 Jan 2026 16:07:32 +0100 Subject: [PATCH 09/16] Fallback SHO to myaccessis.org --- .../files/plugins/microservices/generate_attributes.yaml | 9 +++++++++ roles/midproxy/files/proxy_conf.yaml | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 roles/midproxy/files/plugins/microservices/generate_attributes.yaml diff --git a/roles/midproxy/files/plugins/microservices/generate_attributes.yaml b/roles/midproxy/files/plugins/microservices/generate_attributes.yaml new file mode 100644 index 000000000..3ea26a8d8 --- /dev/null +++ b/roles/midproxy/files/plugins/microservices/generate_attributes.yaml @@ -0,0 +1,9 @@ +module: satosa.micro_services.attribute_generation.AddSyntheticAttributes +name: AddSyntheticAttributes +config: + synthetic_attributes: + default: + default: + schachomeorganization: >- + {{#schachomeorganization}}{{.}}{{/schachomeorganization}} + {{^schachomeorganization}}myaccessid.org{{/schachomeorganization}} diff --git a/roles/midproxy/files/proxy_conf.yaml b/roles/midproxy/files/proxy_conf.yaml index 8af0a3ec7..f2e4bc402 100644 --- a/roles/midproxy/files/proxy_conf.yaml +++ b/roles/midproxy/files/proxy_conf.yaml @@ -17,8 +17,8 @@ FRONTEND_MODULES: - "plugins/frontends/saml2_frontend.yaml" - "plugins/frontends/ping_frontend.yaml" -# MICRO_SERVICES: -# - "plugins/microservices/static_attributes.yaml" +MICRO_SERVICES: + - "plugins/microservices/generate_attributes.yaml" LOGGING: version: 1 From f8746d81bdb447410352e44863bbce28b502fdf9 Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Wed, 7 Jan 2026 17:02:30 +0100 Subject: [PATCH 10/16] Send basic attribute format --- .../files/plugins/attribute-maps/basic.py | 51 +++++++++++++++++++ .../plugins/frontends/saml2_frontend.yaml | 12 ++--- 2 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 roles/midproxy/files/plugins/attribute-maps/basic.py diff --git a/roles/midproxy/files/plugins/attribute-maps/basic.py b/roles/midproxy/files/plugins/attribute-maps/basic.py new file mode 100644 index 000000000..f98466df5 --- /dev/null +++ b/roles/midproxy/files/plugins/attribute-maps/basic.py @@ -0,0 +1,51 @@ +DEF = "urn:mace:dir:attribute-def:" +TERENA = "urn:mace:terena.org:attribute-def:" + +MAP = { + "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", + "fro": { + f"{TERENA}schacHomeOrganization": "schacHomeOrganization", + f"{DEF}cn": "cn", + f"{DEF}displayName": "displayName", + f"{DEF}eduPersonAffiliation": "eduPersonAffiliation", + f"{DEF}eduPersonEntitlement": "eduPersonEntitlement", + f"{DEF}eduPersonPrincipalName": "eduPersonPrincipalName", + f"{DEF}eduPersonScopedAffiliation": "eduPersonScopedAffiliation", + f"{DEF}eduPersonTargetedID": "eduPersonTargetedID", + f"{DEF}eduPersonAssurance": "eduPersonAssurance", + f"{DEF}email": "email", + f"{DEF}emailAddress": "emailAddress", + f"{DEF}givenName": "givenName", + f"{DEF}gn": "gn", + f"{DEF}isMemberOf": "isMemberOf", + f"{DEF}mail": "mail", + f"{DEF}member": "member", + f"{DEF}name": "name", + f"{DEF}sn": "sn", + f"{DEF}surname": "surname", + f"{DEF}uid": "uid", + }, + "to": { + "schacHomeOrganization": f"{TERENA}schacHomeOrganization", + "cn": f"{DEF}cn", + "displayName": f"{DEF}displayName", + "eduPersonAffiliation": f"{DEF}eduPersonAffiliation", + "eduPersonEntitlement": f"{DEF}eduPersonEntitlement", + "eduPersonPrincipalName": f"{DEF}eduPersonPrincipalName", + "eduPersonScopedAffiliation": f"{DEF}eduPersonScopedAffiliation", + "eduPersonTargetedID": f"{DEF}eduPersonTargetedID", + "eduPersonAssurance": f"{DEF}eduPersonAssurance", + "eduPersonOrcid": f"{DEF}eduPersonOrcid", + "email": f"{DEF}email", + "emailAddress": f"{DEF}emailAddress", + "givenName": f"{DEF}givenName", + "gn": f"{DEF}gn", + "isMemberOf": f"{DEF}isMemberOf", + "mail": f"{DEF}mail", + "member": f"{DEF}member", + "name": f"{DEF}name", + "sn": f"{DEF}sn", + "surname": f"{DEF}surname", + "uid": f"{DEF}uid", + }, +} diff --git a/roles/midproxy/files/plugins/frontends/saml2_frontend.yaml b/roles/midproxy/files/plugins/frontends/saml2_frontend.yaml index 059b5bc60..1f8029b66 100644 --- a/roles/midproxy/files/plugins/frontends/saml2_frontend.yaml +++ b/roles/midproxy/files/plugins/frontends/saml2_frontend.yaml @@ -36,7 +36,7 @@ config: local: [!ENV SATOSA_SP_METADATA] entityid: //proxy.xml accepted_time_diff: 60 - # attribute_map_dir: plugins/attribute-maps + attribute_map_dir: plugins/attribute-maps service: idp: endpoints: @@ -56,8 +56,8 @@ config: policy: default: fail_on_missing_requested: false - # name_form: urn:oasis:names:tc:SAML:2.0:attrname-format:basic - # attribute_restrictions: null - # lifetime: {minutes: 15} - # encrypt_assertion: false - # encrypted_advice_attributes: false + name_form: urn:oasis:names:tc:SAML:2.0:attrname-format:basic + attribute_restrictions: null + lifetime: {minutes: 15} + encrypt_assertion: false + encrypted_advice_attributes: false From fb192ce81c5ed7be7c5bef572329de0512c7a9d0 Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Wed, 7 Jan 2026 17:08:04 +0100 Subject: [PATCH 11/16] Fix sHO --- .../files/plugins/microservices/generate_attributes.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/roles/midproxy/files/plugins/microservices/generate_attributes.yaml b/roles/midproxy/files/plugins/microservices/generate_attributes.yaml index 3ea26a8d8..887e7c41e 100644 --- a/roles/midproxy/files/plugins/microservices/generate_attributes.yaml +++ b/roles/midproxy/files/plugins/microservices/generate_attributes.yaml @@ -5,5 +5,4 @@ config: default: default: schachomeorganization: >- - {{#schachomeorganization}}{{.}}{{/schachomeorganization}} - {{^schachomeorganization}}myaccessid.org{{/schachomeorganization}} + myaccessid.org From 44f6e2a2c9c3ee968b61695b80d94eb2db0fb36f Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Wed, 7 Jan 2026 17:19:06 +0100 Subject: [PATCH 12/16] Restart container on deploy --- roles/midproxy/tasks/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/roles/midproxy/tasks/main.yml b/roles/midproxy/tasks/main.yml index 346f56e6e..2962a6f08 100644 --- a/roles/midproxy/tasks/main.yml +++ b/roles/midproxy/tasks/main.yml @@ -33,6 +33,7 @@ pull: true restart_policy: "always" state: started + restart: true networks: - name: "loadbalancer" env: From 440a24c22e6de503b6b797ac7659ad2c5ee9dcdb Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Wed, 7 Jan 2026 18:42:39 +0100 Subject: [PATCH 13/16] Use mail as outgoing mail attribute --- roles/midproxy/files/internal_attributes.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/midproxy/files/internal_attributes.yaml b/roles/midproxy/files/internal_attributes.yaml index f03873371..41ac6e729 100644 --- a/roles/midproxy/files/internal_attributes.yaml +++ b/roles/midproxy/files/internal_attributes.yaml @@ -10,7 +10,7 @@ attributes: saml: [givenName] mail: openid: [email] - saml: [email, emailAddress, mail] + saml: [mail] name: openid: [name] saml: [cn] From f9de8b0ba30635c17df34a2bee541891cef40c0c Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Thu, 8 Jan 2026 08:50:12 +0100 Subject: [PATCH 14/16] Fix uid and sHO --- roles/midproxy/files/internal_attributes.yaml | 5 ----- .../files/plugins/microservices/regex_attributes.yaml | 10 ++++++++++ .../files/plugins/microservices/static_attributes.yaml | 5 ----- roles/midproxy/files/proxy_conf.yaml | 1 + 4 files changed, 11 insertions(+), 10 deletions(-) create mode 100644 roles/midproxy/files/plugins/microservices/regex_attributes.yaml delete mode 100644 roles/midproxy/files/plugins/microservices/static_attributes.yaml diff --git a/roles/midproxy/files/internal_attributes.yaml b/roles/midproxy/files/internal_attributes.yaml index 41ac6e729..eb3dcd66e 100644 --- a/roles/midproxy/files/internal_attributes.yaml +++ b/roles/midproxy/files/internal_attributes.yaml @@ -2,9 +2,6 @@ attributes: displayname: openid: [name] saml: [displayName] - edupersontargetedid: - openid: [sub] - saml: [eduPersonTargetedID] givenname: openid: [given_name] saml: [givenName] @@ -23,5 +20,3 @@ attributes: schachomeorganization: openid: [schac_home_organization] saml: [schacHomeOrganization] -user_id_from_attrs: [edupersontargetedid] -user_id_to_attr: edupersontargetedid diff --git a/roles/midproxy/files/plugins/microservices/regex_attributes.yaml b/roles/midproxy/files/plugins/microservices/regex_attributes.yaml new file mode 100644 index 000000000..e820311e7 --- /dev/null +++ b/roles/midproxy/files/plugins/microservices/regex_attributes.yaml @@ -0,0 +1,10 @@ +module: satosa.micro_services.attribute_processor.AttributeProcessor +name: RegexAttributeProcessor +config: + process: + - attribute: uid + processors: + - name: RegexSubProcessor + module: satosa.micro_services.processors.regex_sub_processor + regex_sub_match_pattern: ^(.+)@.+$ + regex_sub_replace_pattern: \1 diff --git a/roles/midproxy/files/plugins/microservices/static_attributes.yaml b/roles/midproxy/files/plugins/microservices/static_attributes.yaml deleted file mode 100644 index 72e25bb58..000000000 --- a/roles/midproxy/files/plugins/microservices/static_attributes.yaml +++ /dev/null @@ -1,5 +0,0 @@ -module: satosa.micro_services.attribute_modifications.AddStaticAttributes -name: AddAttributes -config: - static_attributes: - schachomeorganization: 'myaccessid.org' diff --git a/roles/midproxy/files/proxy_conf.yaml b/roles/midproxy/files/proxy_conf.yaml index f2e4bc402..136268e61 100644 --- a/roles/midproxy/files/proxy_conf.yaml +++ b/roles/midproxy/files/proxy_conf.yaml @@ -19,6 +19,7 @@ FRONTEND_MODULES: MICRO_SERVICES: - "plugins/microservices/generate_attributes.yaml" + - "plugins/microservices/regex_attributes.yaml" LOGGING: version: 1 From 4f50d422b72d76a5f48f5b7e5e9c3a193b87e8a1 Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Thu, 8 Jan 2026 08:55:43 +0100 Subject: [PATCH 15/16] Fix sHO --- .../files/plugins/microservices/generate_attributes.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/midproxy/files/plugins/microservices/generate_attributes.yaml b/roles/midproxy/files/plugins/microservices/generate_attributes.yaml index 887e7c41e..86ac4e1f1 100644 --- a/roles/midproxy/files/plugins/microservices/generate_attributes.yaml +++ b/roles/midproxy/files/plugins/microservices/generate_attributes.yaml @@ -5,4 +5,4 @@ config: default: default: schachomeorganization: >- - myaccessid.org + {{ uid.scope }} From 3d6197f04df8a763eb8af2cf5052582a5ef0bf58 Mon Sep 17 00:00:00 2001 From: Martin van Es Date: Thu, 19 Mar 2026 16:46:56 +0100 Subject: [PATCH 16/16] Fix commit mistake --- roles/redis/defaults/main.yml | 11 - roles/redis/handlers/main.yml | 6 - roles/redis/tasks/main.yml | 61 ---- roles/redis/templates/redis.conf.j2 | 3 - roles/redis/vars/main.yml | 1 - roles/sbs/defaults/main.yml | 159 ----------- roles/sbs/files/yarn.gpg | 243 ---------------- roles/sbs/handlers/main.yml | 9 - roles/sbs/tasks/main.yml | 171 ------------ roles/sbs/templates/alembic.ini.j2 | 72 ----- roles/sbs/templates/config.yml.j2 | 264 ------------------ roles/sbs/templates/disclaimer.css.j2 | 6 - .../templates/saml_advanced_settings.json.j2 | 35 --- roles/sbs/templates/saml_settings.json.j2 | 22 -- roles/sbs/templates/sbs-apache.conf.j2 | 30 -- roles/sbs/templates/sbs.service.j2 | 32 --- roles/sbs/vars/main.yml | 1 - 17 files changed, 1126 deletions(-) delete mode 100644 roles/redis/defaults/main.yml delete mode 100644 roles/redis/handlers/main.yml delete mode 100644 roles/redis/tasks/main.yml delete mode 100644 roles/redis/templates/redis.conf.j2 delete mode 100644 roles/redis/vars/main.yml delete mode 100644 roles/sbs/defaults/main.yml delete mode 100644 roles/sbs/files/yarn.gpg delete mode 100644 roles/sbs/handlers/main.yml delete mode 100644 roles/sbs/tasks/main.yml delete mode 100644 roles/sbs/templates/alembic.ini.j2 delete mode 100644 roles/sbs/templates/config.yml.j2 delete mode 100644 roles/sbs/templates/disclaimer.css.j2 delete mode 100644 roles/sbs/templates/saml_advanced_settings.json.j2 delete mode 100644 roles/sbs/templates/saml_settings.json.j2 delete mode 100644 roles/sbs/templates/sbs-apache.conf.j2 delete mode 100644 roles/sbs/templates/sbs.service.j2 delete mode 100644 roles/sbs/vars/main.yml diff --git a/roles/redis/defaults/main.yml b/roles/redis/defaults/main.yml deleted file mode 100644 index d4eb4b182..000000000 --- a/roles/redis/defaults/main.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -redis: "{{ redis_defaults | combine(redis_overrides, recursive=true) }}" -redis_defaults: - image: "docker.io/library/redis:7" - conf_dir: "{{ current_release_appdir }}/redis" - data_dir: "{{ current_release_appdir }}/redis/data" - user: redis - group: redis - redis_user: default - redis_password: changethispassword - max_memory: 100mb diff --git a/roles/redis/handlers/main.yml b/roles/redis/handlers/main.yml deleted file mode 100644 index 5ed78e133..000000000 --- a/roles/redis/handlers/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: Restart redis container - community.docker.docker_container: - name: redis - state: started - restart: true diff --git a/roles/redis/tasks/main.yml b/roles/redis/tasks/main.yml deleted file mode 100644 index 65e7392ef..000000000 --- a/roles/redis/tasks/main.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -- name: "Create redis group" - group: - name: "{{ redis.group }}" - state: "present" - register: "result" - -- name: "Save redis group gid" - set_fact: - redis_group_gid: "{{ result.gid }}" - -- name: "Create redis user" - user: - name: "{{ redis.user }}" - group: "{{ redis.group }}" - comment: "User to run SRAM Redis service" - shell: "/bin/false" - password: "!" - home: "{{ redis.conf_dir }}" - create_home: false - state: "present" - register: "result" - -- name: "Save redis user uid" - set_fact: - redis_user_uid: "{{ result.uid }}" - -- name: "Create directories" - file: - path: "{{item.path}}" - state: "directory" - owner: "{{ redis.user }}" - group: "{{ redis.group }}" - mode: "{{item.mode}}" - with_items: - - { path: "{{redis.conf_dir}}", mode: "0755" } - - { path: "{{redis.data_dir}}", mode: "0755" } - -- name: "Create redis config" - template: - src: "redis.conf.j2" - dest: "{{ redis.conf_dir }}/redis.conf" - owner: "{{ redis.user }}" - group: "{{ redis.group }}" - mode: "0644" - notify: "Restart redis container" - -- name: "Create redis container" - community.docker.docker_container: - name: "redis" - image: "{{ redis.image }}" - restart_policy: "always" - state: "started" - user: "{{ redis_user_uid }}:{{ redis_group_gid }}" - command: | - redis-server /usr/local/etc/redis/redis.conf - volumes: - - "{{ redis.conf_dir }}:/usr/local/etc/redis" - - "{{ redis.data_dir }}:/data" - networks: - - name: loadbalancer diff --git a/roles/redis/templates/redis.conf.j2 b/roles/redis/templates/redis.conf.j2 deleted file mode 100644 index ba231dc58..000000000 --- a/roles/redis/templates/redis.conf.j2 +++ /dev/null @@ -1,3 +0,0 @@ -user {{redis.redis_user}} on +@all ~* &* >{{redis.redis_password}} -maxmemory {{ redis.max_memory }} -maxmemory-policy allkeys-lru diff --git a/roles/redis/vars/main.yml b/roles/redis/vars/main.yml deleted file mode 100644 index 761942f7b..000000000 --- a/roles/redis/vars/main.yml +++ /dev/null @@ -1 +0,0 @@ -current_release_appdir: /opt/openconext diff --git a/roles/sbs/defaults/main.yml b/roles/sbs/defaults/main.yml deleted file mode 100644 index f21addf5f..000000000 --- a/roles/sbs/defaults/main.yml +++ /dev/null @@ -1,159 +0,0 @@ ---- -sbs: "{{ sbs_defaults | combine(sbs_overrides, recursive=true) }}" -sbs_defaults: - openidc_timeout: 86400 - sram_conf_dir: "{{ current_release_appdir }}/sram" - - work_dir: "{{ sram_conf_dir }}/sbs" - git_dir: "{{ sbs.work_dir }}/sbs" - env_dir: "{{ sbs.work_dir }}/sbs-env" - conf_dir: "{{ sbs.work_dir }}/config" - log_dir: "{{ sbs.work_dir }}/log" - cert_dir: "{{ sbs.work_dir }}/cert" - apache_conf: "{{ sbs.work_dir }}/sbs.conf" - nginx_conf: "{{ sbs.work_dir }}/nginx.conf" - - - db_name: "sbs" - db_user: "sbsrw" - dbbackup_user: "sbs_backupper" - migration_user: "sbs_migrater" - - db_connection: "\ - mysql+mysqldb://%s:%s@{{ mariadb_host }}/{{ sbs_db_name }}\ - ?ssl=true&charset=utf8mb4" - db_connection_sbs: "{{ sbs_db_connection | format(sbs_db_user, sbs_db_password) }}" - db_connection_migration: "\ - {{ sbs_db_connection | format(sbs_migration_user, sbs_migration_password) }}" - - redis_host: redis - redis_port: 6379 - redis_ssl: false - redis_user: default - - mail_host: "{{ mail.relay_to }}" - mail_port: "{{ mail.relay_port }}" - - user: "sbs" - group: "sbs" - - session_lifetime: 1440 - secret_key_suffix: "" - - oidc_crypto_password: "CHANGEME" - uid_attribute: "sub" - - disclaimer_color: "#a29c13" - disclaimer_label: wsgi - - urn_namespace: "urn:example:sbs" - eppn_scope: "sbs.example.edu" - restricted_co_default_org: "example.org" - - mail_sender_name: "SURF" - mail_sender_email: "no-reply@localhost" - exceptions_mail: "root@localhost" - - support_email: "sram-support@localhost" - admin_email: "sram-beheer@localhost" - ticket_email: "sram-support@surf.nl" - eduteams_email: "eduteams@localhost" - - wiki_link: "https://www.example.org/wiki" - - backend_port: 8080 - num_workers: 2 - - cron_hour_of_day: 4 - seed_allowed: True - api_keys_enabled: True - feedback_enabled: True - audit_trail_notifications_enabled: True - send_exceptions: False - send_js_exceptions: False - second_factor_authentication_required: True - totp_token_name: "SRAM-example" - notifications_enabled: True - invitation_reminders_enabled: True - invitation_expirations_enabled: True - open_requests_enabled: True - scim_sweep: False - impersonation_allowed: True - admin_platform_backdoor_totp: True - past_dates_allowed: True - mock_scim_enabled: True - log_to_stdout: True - - delete_orphaned: True - suspension_inactive_days: 365 - suspension_reminder_days: 14 - suspension_notify_admin: False - - oidc_config_url: "http://localhost/.well-known/openid-configuration" - oidc_authz_endpoint: "http://localhost/OIDC/authorization" - oidc_token_endpoint: "http://localhost/OIDC/token" - oidc_userinfo_endpoint: "http://localhost/OIDC/userinfo" - oidc_jwks_endpoint: "http://localhost/OIDC/jwks.json" - oidc_redirect_uri: "https://sbs.scz-vm.net/api/users/resume-session" - mfa_idp_allowed: false - eduteams_continue_endpoint: "https://localhost/continue" - eb_continue_endpoint: "https://engine.(.*)surfconext.nl(.*)" - oidc_jwt_audience: "https://localhost" - continue_eduteams_redirect_uri: "https://localhost/continue" - oidc_verify_peer: False - oidc_scopes: - - openid - - manage_base_enabled: False - manage_base_url: "https://manage.test2.surfconext.nl" - manage_sram_rp_entity_id: "sbs.test2.sram.surf.nl" - manage_verify_peer: False - - idp_metadata_url: "https://metadata.surfconext.nl/signed/2023/edugain-downstream-idp.xml " - backup_dir: "{{backup_base}}/sbs" - - swagger_enabled: true - - ssid_identity_providers: [] - surf_secure_id: - environment: "unknown.example.org" - sp_entity_id: "https://sbs.{{base_domain}}" - acs_url: "https://{{base_domain}}/api/users/acs" - sa_gw_environment: "sa-gw.unknown.example.org" - sa_idp_certificate: | - -----BEGIN CERTIFICATE----- - 12345 - -----END CERTIFICATE----- - priv: | - -----BEGIN RSA PRIVATE KEY----- - abcde - -----END RSA PRIVATE KEY----- - pub: | - -----BEGIN CERTIFICATE----- - 12345 - -----END CERTIFICATE----- - - ssid_authncontext: "\ - http://{{ sbs.surf_secure_id.environment }}/assurance/sfo-level2" - ssid_entityid: "\ - https://{{ sbs.surf_secure_id.sa_gw_environment }}/second-factor-only/metadata" - ssid_sso_endpoint: "\ - https://{{ sbs.surf_secure_id.sa_gw_environment }}/second-factor-only/single-sign-on" - - mfa_sso_minutes: 10 - mfa_fallback_enabled: true - - ldap_url: "ldap://ldap.example.com/dc=example,dc=com" - ldap_bind_account: "cn=admin,dc=entity_id,dc=services,dc=sram-tst,dc=surf,dc=nl" - - csp_style_hashes: - - 'sha256-0+ANsgYUJdh56RK8gGvTF2vnriYqvFHfWqtA8xXa+bA=' - - 'sha256-3SnfHQolDHbZMbDAPmhrZf1keHiXfj/KJyh2phhFAAY=' - - 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=' - - 'sha256-Ng6y+QCkPChG4Q49SIfXB5ToIDcDhITtQNFkDBPpCTw=' - - 'sha256-orBPipbqpMvkNi+Z+m6qEn0XS6ymmAQE6+FwCNs1FbQ=' - - 'sha256-vFt3L2qLqpJmRpcXGbYr2UVSmgSp9VCUzz2lnqWIATw=' - - 'sha256-SU3XCwbQ/8qgzoGOWCYdkwIr3xRrl5rsvdFcpw8NSiE=' # on /new-service-request - - 'sha256-WTC9gHKjIpzl5ub1eg/YrRy/k+jlzeyRojah9dxAApc=' # on /new-service-request - - engine_block_api_token: secret diff --git a/roles/sbs/files/yarn.gpg b/roles/sbs/files/yarn.gpg deleted file mode 100644 index 3e9e7d155..000000000 --- a/roles/sbs/files/yarn.gpg +++ /dev/null @@ -1,243 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1 - -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 -=oMDY ------END PGP PUBLIC KEY BLOCK----- diff --git a/roles/sbs/handlers/main.yml b/roles/sbs/handlers/main.yml deleted file mode 100644 index 012cba535..000000000 --- a/roles/sbs/handlers/main.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -- name: Restart sbs containers - community.docker.docker_container: - name: "{{ item }}" - state: started - restart: true - loop: - - sbs - - sbs_server diff --git a/roles/sbs/tasks/main.yml b/roles/sbs/tasks/main.yml deleted file mode 100644 index 8a52adc4e..000000000 --- a/roles/sbs/tasks/main.yml +++ /dev/null @@ -1,171 +0,0 @@ ---- -- name: "Create SBS group" - group: - name: "{{ sbs.group }}" - state: "present" - register: "result" - -- name: "Save SBS group gid" - set_fact: - sbs_group_gid: "{{ result.gid }}" - -- name: "Create SBS user" - user: - name: "{{ sbs.user }}" - group: "{{ sbs.group }}" - comment: "User to run SBS service" - shell: "/bin/false" - password: "!" - home: "{{ sbs.conf_dir }}" - create_home: false - state: "present" - register: "result" - -- name: "Save sbs user uid" - set_fact: - sbs_user_uid: "{{ result.uid }}" - -- name: "Create directories" - file: - path: "{{item.path}}" - state: "directory" - owner: "root" - group: "{{sbs_group_gid}}" - mode: "{{item.mode}}" - with_items: - - { path: "{{sbs.work_dir}}", mode: "0755" } - - { path: "{{sbs.conf_dir}}", mode: "0755" } - - { path: "{{sbs.conf_dir}}/saml", mode: "0755" } - - { path: "{{sbs.log_dir}}", mode: "0775" } - - { path: "{{sbs.cert_dir}}", mode: "0755" } - -- name: "Fix file permissions" - file: - path: "{{sbs.log_dir}}/{{item}}" - owner: "root" - group: "{{sbs_group_gid}}" - mode: "0664" - state: "touch" - modification_time: "preserve" - access_time: "preserve" - with_items: - - "sbs.log" - - "sbs_debug.log" - -- name: "Copy wildcard backend cert" - copy: - content: "{{wildcard_backend_cert.pub}}" - dest: "{{sbs.cert_dir}}/backend.crt" - owner: "root" - group: "root" - mode: "0644" - notify: "Restart sbs containers" - -- name: "Copy https cert" - copy: - content: "{{https_cert.cert}}" - dest: "{{sbs.cert_dir}}/frontend.crt" - owner: "root" - group: "root" - mode: "0644" - notify: "Restart sbs containers" - -- name: "Install database certificate" - copy: - dest: "{{sbs.db_cert_path}}" - content: "{{ sbs.db_tls_cert }}" - owner: "root" - group: "root" - mode: "0644" - -- name: "Create SBS config files" - template: - src: "{{item.name}}.j2" - dest: "{{ sbs.conf_dir }}/{{item.name}}" - owner: "root" - group: "{{sbs_group_gid}}" - mode: "{{item.mode}}" - with_items: - - { name: "config.yml", mode: "0644" } - - { name: "alembic.ini", mode: "0644" } - - { name: "disclaimer.css", mode: "0644" } - - { name: "sbs-apache.conf", mode: "0644" } - no_log: "{{sram_ansible_nolog}}" - notify: "Restart sbs containers" - -- name: "Run SBS migrations" - throttle: 1 - community.docker.docker_container: - name: "sbs_migration" - image: "{{ sbs.server_image }}" - pull: "never" - state: "started" - restart_policy: "no" - detach: false - env: - RUNAS_UID: "{{ sbs_user_uid | string }}" - RUNAS_GID: "{{ sbs_group_gid | string }}" - CONFIG: "/opt/sbs/server/config/config.yml" - MIGRATIONS_ONLY: "1" - # don't actually run the server - command: "/bin/true" - volumes: - - "{{ sbs.conf_dir }}:/sbs-config" - - "{{ sbs.cert_dir }}:/sbs-config/cert" - - "{{ sbs.log_dir }}:/opt/sbs/log" - networks: - - name: "{{internal_network}}" - register: "result" - failed_when: "'container' not in result or result.container.State.ExitCode != 0" - changed_when: "'[alembic.runtime.migration] Running upgrade' in result.container.Output" - notify: "Restart sbs containers" - -# Remove the migration container; we can't do that with auto_remove, because if we use that, ansible -# will not save the output in result -- name: "Remove migration container" - community.docker.docker_container: - name: "sbs_migration" - state: "absent" - # TODO: fix this by only running this if "sbs_image is changed" - changed_when: false - -- name: "Start sbs container" - community.docker.docker_container: - name: "sbs" - image: "{{ sbs.image }}" - pull: "never" - restart_policy: "always" - state: "started" - env: - RUN_MIGRATIONS: "0" - volumes: - - "{{ sbs.conf_dir }}/sbs-apache.conf:/etc/apache2/sites-enabled/sbs.conf:ro" - networks: - - name: loadbalancer - labels: - traefik.enable: "true" - traefik.docker.network: "{{traefik_network}}" - traefik.http.routers.sbs.rule: "Host(`{{ sbs.base_domain }}`)" - traefik.http.routers.sbs.tls: "true" - -- name: "Start SBS server container" - community.docker.docker_container: - name: "sbs_server" - image: "{{ sbs.server_image }}" - restart_policy: "always" - state: "started" - env: - RUNAS_UID: "{{ sbs_user_uid | string }}" - RUNAS_GID: "{{ sbs_group_gid | string }}" - CONFIG: "/opt/sbs/server/config/config.yml" - REQUESTS_CA_BUNDLE: "/etc/ssl/certs/ca-certificates.crt" - RUN_MIGRATIONS: "0" - pull: "always" - command: "/usr/local/bin/gunicorn --preload --worker-class eventlet --workers 8 --bind 0.0.0.0:8080 server.__main__:app" - volumes: - - "{{ sbs.conf_dir }}:/sbs-config" - - "{{ sbs.cert_dir }}:/sbs-config/cert" - - "{{ sbs.log_dir }}:/opt/sbs/log" - - "/tmp/ci-runner:/tmp/ci-runner" - networks: - - name: loadbalander diff --git a/roles/sbs/templates/alembic.ini.j2 b/roles/sbs/templates/alembic.ini.j2 deleted file mode 100644 index 7849e4f89..000000000 --- a/roles/sbs/templates/alembic.ini.j2 +++ /dev/null @@ -1,72 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts -script_location = migrations - -# template used to generate migration files -# file_template = %%(rev)s_%%(slug)s - -# timezone to use when rendering the date -# within the migration file as well as the filename. -# string value is passed to dateutil.tz.gettz() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the -# "slug" field -#truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; this defaults -# to alembic/versions. When using multiple version -# directories, initial revisions must be specified with --version-path -# version_locations = %(here)s/bar %(here)s/bat alembic/versions - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -sqlalchemy.url = {{ sbs_db_connection_migration }} - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = NOTSET -handlers = console - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = DEBUG -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/roles/sbs/templates/config.yml.j2 b/roles/sbs/templates/config.yml.j2 deleted file mode 100644 index 11f029af7..000000000 --- a/roles/sbs/templates/config.yml.j2 +++ /dev/null @@ -1,264 +0,0 @@ ---- -database: - uri: {{ sbs.db_connection_sbs }} - -redis: -{% if environment_name == 'tst2' %} - uri: "redis://{{redis_vhost}}/" -{% else %} - uri: "redis{% if sbs.redis_ssl %}s{% endif %}://{{ sbs.redis_user }}:{{ sbs.redis_password }}@{{ sbs.redis_host }}:{{ sbs.redis_port }}/" -{% endif %} - -# add a per-release suffix here to invalidate sessions on new releases -secret_key: {{ sbs.db_secret }}{{sbs.secret_key_suffix}} -# Must be a base64 encoded key of 128, 192, or 256 bits. -# Generate: base64.b64encode(os.urandom(256 // 8)).decode() -encryption_key: {{ sbs.encryption_key }} - -# Lifetime of session in minutes (one day is 60 * 24) -permanent_session_lifetime: {{ sbs.session_lifetime }} - -logging: - log_to_stdout: {{ sbs.log_to_stdout }} - -# Valid scopes are "READ" and "WRITE" -api_users: -{% for name, user in sbs.api_users.items() %} - - name: "{{ name }}" - password: "{{ user.password }}" - scopes: "[ {{ user.scopes | join(', ') }} ]" -{% endfor %} - -oidc: - client_id: "{{ sbs.client_id }}" - client_secret: "{{ sbs.client_secret }}" - audience: "{{ sbs.oidc_jwt_audience }}" - verify_peer: {{ sbs.oidc_verify_peer }} - authorization_endpoint: "{{ sbs.oidc_authz_endpoint}}" - token_endpoint: "{{ sbs.oidc_token_endpoint }}" - userinfo_endpoint: "{{ sbs.oidc_userinfo_endpoint }}" - jwks_endpoint: "{{ sbs.oidc_jwks_endpoint }}" - #Note that the paths for these uri's is hardcoded and only domain and port differ per environment - redirect_uri: "{{ sbs.oidc_redirect_uri }}" - continue_eduteams_redirect_uri: "{{ sbs.eduteams_continue_endpoint }}" - continue_eb_redirect_uri: "{{ sbs.eb_continue_endpoint }}" - second_factor_authentication_required: {{ sbs.second_factor_authentication_required }} - totp_token_name: "{{ sbs.totp_token_name }}" - # The service_id in the proxy_authz endpoint when logging into SBS. Most likely to equal the oidc.client_id - sram_service_entity_id: "{{ sbs.client_id }}" - scopes: {{ sbs.oidc_scopes }} - -base_scope: "{{ base_domain }}" -entitlement_group_namespace: "{{ sbs.urn_namespace }}" -eppn_scope: " {{ sbs.eppn_scope }}" -scim_schema_sram: "urn:mace:surf.nl:sram:scim:extension" -collaboration_creation_allowed_entitlement: "urn:mace:surf.nl:sram:allow-create-co" - -{% if environment_name == "prd" %} -environment_disclaimer: "" -{% else %} -environment_disclaimer: "{{ sbs.disclaimer_label }}" -{% endif %} - -# All services in the white list can be requested in the create-restricted-co API -# The default organisation is a fallback for when the administrator has no schac_home_org -restricted_co: - services_white_list: [ "https://cloud" ] - default_organisation: "{{ sbs.restricted_co_default_org }}" - -mail: - host: {{ sbs.mail_host }} - port: {{ sbs.mail_port }} - sender_name: {{ sbs.mail_sender_name }} - sender_email: {{ sbs.mail_sender_email }} - suppress_sending_mails: False - info_email: {{ sbs.support_email }} - beheer_email: {{ sbs.admin_email }} - ticket_email: {{ sbs.ticket_email }} - eduteams_email: {{ sbs.eduteams_email }} - # Do we mail a summary of new Organizations and Services to the beheer_email? - audit_trail_notifications_enabled: {{ sbs.audit_trail_notifications_enabled }} - account_deletion_notifications_enabled: True - send_exceptions: {{ sbs.send_exceptions }} - send_js_exceptions: {{ sbs.send_js_exceptions }} - send_exceptions_recipients: [ "{{ sbs.exceptions_mail }}" ] - environment: "{{ base_domain }}" - -manage: - enabled: {{ sbs.manage_base_enabled }} - # The entity_id of the SRAM RP in Manage for API retrieval, e.g "sbs.test2.sram.surf.nl" - sram_rp_entity_id: "{{ sbs.manage_sram_rp_entity_id }}" - base_url: "{{ sbs.manage_base_url }}" - user: "{{ sbs.manage_user }}" - password: "{{ sbs.manage_password }}" - verify_peer: {{ sbs.manage_verify_peer }} - -aup: - version: 1 - url_aup_en: "https://edu.nl/6wb63" - url_aup_nl: "https://edu.nl/6wb63" - -base_url: {{ sbs.base_url }} -socket_url: {{ sbs.base_url }} -base_server_url: {{ sbs.base_url }} -wiki_link: {{ sbs.wiki_link }} - -admin_users: -{% for admin_user in sbs.admin_users %} - - uid: "{{ admin_user.uid }}" -{% endfor %} - -organisation_categories: - - "HBO" - - "MBO" - - "UMC" - - "University" - - "Research" - - "SURF" - -feature: - seed_allowed: {{ sbs.seed_allowed }} - api_keys_enabled: {{ sbs.api_keys_enabled }} - feedback_enabled: {{ sbs.feedback_enabled }} - impersonation_allowed: {{ sbs.impersonation_allowed }} - sbs_swagger_enabled: {{ sbs.swagger_enabled }} - admin_platform_backdoor_totp: {{ sbs.admin_platform_backdoor_totp }} - past_dates_allowed: {{ sbs.past_dates_allowed }} - mock_scim_enabled: {{ sbs.mock_scim_enabled }} - -metadata: - idp_url: "{{sbs.idp_metadata_url}}" - parse_at_startup: True - # No need for environment specific values - scope_override: - knaw.nl: "Koninklijke Nederlandse Akademie van Wetenschappen (KNAW)" - -platform_admin_notifications: - # Do we daily check for CO join_requests and CO requests and send a summary mail to beheer_email? - enabled: False - cron_hour_of_day: {{ sbs.cron_hour_of_day }} - # How long before we include open join_requests in the summary - outstanding_join_request_days_threshold: 7 - # How long before we include open CO requests in the summary - outstanding_coll_request_days_threshold: 7 - -user_requests_retention: - # Do we daily check for CO join_requests and CO requests and delete approved and denied? - enabled: {{ sbs.notifications_enabled }} - cron_hour_of_day: {{ sbs.cron_hour_of_day }} - # How long before we delete approved / denied join_requests - outstanding_join_request_days_threshold: 90 - # How long before we delete approved / denied CO requests - outstanding_coll_request_days_threshold: 90 - -# The retention config determines how long users may be inactive, how long the reminder email is valid and when do we resent the magic link -retention: - cron_hour_of_day: {{ sbs.cron_hour_of_day }} - # how many days of inactivity before a user is suspended - # 0 allows for any last_login_date in the past to trigger suspension notification - allowed_inactive_period_days: {{ sbs.suspension_inactive_days }} - # how many days before suspension do we send a warning - # -1 will suspend notified users on second suspension cron - reminder_suspend_period_days: {{ sbs.suspension_reminder_days }} - # how many days after suspension do we delete the account - remove_suspended_users_period_days: 90 - # how many days before deletion do we send a reminder - reminder_expiry_period_days: 7 - # whether to send a notification of the result of the retention process to the beheer_email - admin_notification_mail: {{ sbs.suspension_notify_admin }} - -collaboration_expiration: - # Do we daily check for CO's that will be deleted because they have been expired? - enabled: {{ sbs.notifications_enabled }} - cron_hour_of_day: {{ sbs.cron_hour_of_day }} - # How long after expiration do we actually delete expired collaborations - expired_collaborations_days_threshold: 90 - # How many days before actual expiration do we mail the organisation members - expired_warning_mail_days_threshold: 10 - -collaboration_suspension: - # Do we daily check for CO's that will be suspended because of inactivity? - enabled: {{ sbs.notifications_enabled }} - cron_hour_of_day: {{ sbs.cron_hour_of_day }} - # After how many days of inactivity do we suspend collaborations - collaboration_inactivity_days_threshold: 365 - # How many days before actual suspension do we mail the organisation members - inactivity_warning_mail_days_threshold: 10 - # After how many days after suspension do we actually delete the collaboration - collaboration_deletion_days_threshold: 90 - -membership_expiration: - # Do we daily check for memberships that will be deleted because they have been expired? - enabled: {{ sbs.notifications_enabled }} - cron_hour_of_day: {{ sbs.cron_hour_of_day }} - # How long after expiration do we actually delete expired memberships - expired_memberships_days_threshold: 90 - # How many days before actual expiration do we mail the co admin and member - expired_warning_mail_days_threshold: 10 - -invitation_reminders: - # Do we daily check for invitations that need a reminder? - enabled: {{ sbs.invitation_reminders_enabled }} - cron_hour_of_day: {{ sbs.cron_hour_of_day }} - # How many days before expiration of an invitation do we remind the user? - invitation_reminders_threshold: 5 - -invitation_expirations: - # Do we daily check for invitations that are expired / accepted and are eligible for deletion ? - enabled: {{ sbs.invitation_expirations_enabled }} - cron_hour_of_day: {{ sbs.cron_hour_of_day }} - # How long after expiration of an invitation do we delete the invitation? - nbr_days_remove_expired_invitations: 10 - # How long after expiration of an API created invitation do we delete the invitation? - nbr_days_remove_api_expired_invitations: 30 - -orphan_users: - # Do we daily check for users that are orphans soo they can be deleted? - enabled: {{ sbs.delete_orphaned }} - cron_hour_of_day: {{ sbs.cron_hour_of_day }} - # How long after created do we delete orphan users - delete_days_threshold: 14 - -open_requests: - # Do we weekly check for all open requests? - enabled: {{ sbs.open_requests_enabled }} - cron_day_of_week: 1 - -scim_sweep: - # Do we enable scim sweeps? - enabled: {{ sbs.scim_sweep }} - # How often do we check if scim sweeps are needed per service - cron_minutes_expression: "*/15" - -ldap: - url: "{{ sbs.ldap_url }}" - bind_account: "{{ sbs.ldap_bind_account }}" - -# A MFA login in a different flow is valid for X minutes -mfa_sso_time_in_minutes: {{sbs.mfa_sso_minutes}} - -# whether to fall back to TOTP MFA -mfa_fallback_enabled: {{sbs.mfa_fallback_enabled}} - -# Lower case entity ID's and schac_home allowed skipping MFA. -# Note that for a login directly into SRAM only schac_home can be used as the entity_idp of the IdP is unknown -mfa_idp_allowed: {{sbs.mfa_idp_allowed}} - -# Lower case schachome organisations / entity ID's where SURFSecure ID is used for step-up -ssid_identity_providers: {{sbs.ssid_identity_providers}} - -ssid_config_folder: saml - -pam_web_sso: - session_timeout_seconds: 300 - -rate_limit_totp_guesses_per_30_seconds: 10 - -# The uid's of user that will never be suspended or deleted -excluded_user_accounts: -{% for excluded_user in sbs.excluded_users %} - - uid: "{{ excluded_user.uid }}" -{% endfor %} - -engine_block: - api_token: {{ sbs.engine_block_api_token }} diff --git a/roles/sbs/templates/disclaimer.css.j2 b/roles/sbs/templates/disclaimer.css.j2 deleted file mode 100644 index e89bbfce7..000000000 --- a/roles/sbs/templates/disclaimer.css.j2 +++ /dev/null @@ -1,6 +0,0 @@ -{% if environment_name!="prd" -%} -body::after { - background: {{ sbs_disclaimer_color }}; - content: "{{ sbs_disclaimer_label }}"; -} -{% endif %} diff --git a/roles/sbs/templates/saml_advanced_settings.json.j2 b/roles/sbs/templates/saml_advanced_settings.json.j2 deleted file mode 100644 index bdde32050..000000000 --- a/roles/sbs/templates/saml_advanced_settings.json.j2 +++ /dev/null @@ -1,35 +0,0 @@ -{ - "security": { - "nameIdEncrypted": false, - "authnRequestsSigned": true, - "logoutRequestSigned": false, - "logoutResponseSigned": false, - "signMetadata": false, - "wantMessagesSigned": false, - "wantAssertionsSigned": true, - "wantNameId" : true, - "wantNameIdEncrypted": false, - "wantAttributeStatement": false, - "wantAssertionsEncrypted": false, - "requestedAuthnContext": ["{{sbs_ssid_authncontext}}"], - "requestedAuthnContextComparison": "minimum", - "failOnAuthnContextMismatch": false, - "allowSingleLabelDomains": false, - "signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", - "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256", - "rejectDeprecatedAlgorithm": true - }, - "contactPerson": { - "technical": { - "givenName": "{{ mail.admin_name }}", - "emailAddress": "{{ mail.admin_address }}" - } - }, - "organization": { - "en-US": { - "name": "{{ org.name }}", - "displayname": "{{ org.name }}", - "url": "{{ org.url }}" - } - } -} diff --git a/roles/sbs/templates/saml_settings.json.j2 b/roles/sbs/templates/saml_settings.json.j2 deleted file mode 100644 index bb5788e97..000000000 --- a/roles/sbs/templates/saml_settings.json.j2 +++ /dev/null @@ -1,22 +0,0 @@ -{ - "strict": true, - "debug": true, - "sp": { - "entityId": "{{ sbs_surf_secure_id.sp_entity_id }}", - "assertionConsumerService": { - "url": "{{ sbs_surf_secure_id.acs_url }}", - "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" - }, - "NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", - "x509cert": "{{ sbs_surf_secure_id.pub | barepem }}", - "privateKey": "{{ sbs_surf_secure_id.priv | barepem }}" - }, - "idp": { - "entityId": "{{ sbs_ssid_entityid }}", - "singleSignOnService": { - "url": "{{ sbs_ssid_sso_endpoint }}", - "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" - }, - "x509cert": "{{ sbs_surf_secure_id.sa_idp_certificate | barepem }}" - } -} diff --git a/roles/sbs/templates/sbs-apache.conf.j2 b/roles/sbs/templates/sbs-apache.conf.j2 deleted file mode 100644 index 13752ee56..000000000 --- a/roles/sbs/templates/sbs-apache.conf.j2 +++ /dev/null @@ -1,30 +0,0 @@ -ServerName {{ hostnames.sbs }} -#ErrorLog /proc/self/fd/2 -#CustomLog /proc/self/fd/1 common -DocumentRoot /opt/sbs/client/build - -Header set Content-Security-Policy "default-src 'self'; base-uri 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-src 'none'; form-action 'self' https://*.{{ base_domain }}; frame-ancestors 'none'; block-all-mixed-content;" -Header set Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=(), clipboard-read=(), clipboard-write=(self), gamepad=(), speaker-selection=()" - -RewriteEngine On -RewriteCond %{REQUEST_URI} !^/(api|pam-weblogin|flasgger_static|swagger|health|config|info|socket.io) -RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f -RewriteRule ^/(.*)$ /index.html [L] - -ProxyRequests off -ProxyPassMatch ^/(api|pam-weblogin|flasgger_static|swagger|health|config|info) http://{{ containers.sbs_server }}:{{sbs_backend_port}}/ -ProxyPassReverse / http://{{ containers.sbs_server }}:{{sbs_backend_port}}/ -ProxyPass /socket.io/ ws://{{ containers.sbs_server }}:{{sbs_backend_port}}/socket.io/ -ProxyPassReverse /socket.io/ ws://{{ containers.sbs_server }}:{{sbs_backend_port}}/socket.io/ - - - Header set Cache-Control: "public, max-age=31536000, immutable" - - - Header set Cache-Control: "no-cache, private" - - - - Require all granted - Options -Indexes - diff --git a/roles/sbs/templates/sbs.service.j2 b/roles/sbs/templates/sbs.service.j2 deleted file mode 100644 index 2920ddc8d..000000000 --- a/roles/sbs/templates/sbs.service.j2 +++ /dev/null @@ -1,32 +0,0 @@ -[Unit] -Description=SBS -After=network.target - -[Service] -DynamicUser=true -User=_sram_sbs -Group=_sram_sbs -SupplementaryGroups={{sbs_group}} - -WorkingDirectory={{sbs_git_dir}} -ReadWritePaths={{sbs_log_dir}} -NoNewPrivileges=true -PrivateTmp=true - -Environment="CONFIG=config/config.yml" -Environment="PROFILE=log_to_stdout" -# the python requests module uses the CAs provided by the certifi package by default -# we'll just take the OS-provided CAs, thankyouverymuch -Environment="REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt" - -Type=notify -ExecStart={{sbs_env_dir}}/bin/gunicorn --worker-class eventlet --workers {{sbs_num_workers}} --bind 127.0.0.1:8080 server.__main__:app - -Restart=on-failure -RestartSec=10 - -KillMode=mixed -TimeoutStopSec=5 - -[Install] -WantedBy=multi-user.target diff --git a/roles/sbs/vars/main.yml b/roles/sbs/vars/main.yml deleted file mode 100644 index 761942f7b..000000000 --- a/roles/sbs/vars/main.yml +++ /dev/null @@ -1 +0,0 @@ -current_release_appdir: /opt/openconext