diff --git a/docs/reference.md b/docs/reference.md index ab3e5a25eb9d533e54741c60b57cab52cec3329b..af8425cb586794f20e928d886d9681230fe53008 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -2088,6 +2088,12 @@ using single sign-on, allowing access only to administrators (members of the *admins* group). This is quite useful for admin web interfaces of internal services that do not support SSO integration of their own. +`enable_api_proxy`: If true, place the service behind authentication +using a mechanism more appropriate for non-interactive APIs (HTTP +Basic Authentication using Application-Specific Passwords). Only members +of the *admins* group will have access. When this option is set, you +also need to specify a unique `auth_service` to be used for ASPs. + #### HTTP (All domains) `horizontal_endpoints`: List of HTTP endpoints exported by the diff --git a/docs/reference.pdf b/docs/reference.pdf index ac02d89969fb52c08f50515ae1be4eb588125181..20cdfe312c22e9bb0c692c41f91d48381801eac8 100644 Binary files a/docs/reference.pdf and b/docs/reference.pdf differ diff --git a/plugins/inventory/float.py b/plugins/inventory/float.py index e67df79ecb905ae7792d9ae24b952ab3ef688613..17685c3358e54554f9df0b76a807f0b0974fa640 100644 --- a/plugins/inventory/float.py +++ b/plugins/inventory/float.py @@ -332,6 +332,7 @@ def _build_public_endpoints_map(services): 'name': upstream_name, 'service_name': service_name, 'port': pe['port'], + 'enable_api_proxy': pe.get('enable_api_proxy', False), 'enable_sso_proxy': pe.get('enable_sso_proxy', False), 'sharded': pe.get('sharded', False), } @@ -385,6 +386,7 @@ def _build_horizontal_upstreams_map(services): 'name': upstream_name, 'service_name': service_name, 'port': ep['port'], + 'enable_api_proxy': False, 'enable_sso_proxy': False, 'sharded': False, } diff --git a/roles/float-infra-nginx/handlers/main.yml b/roles/float-infra-nginx/handlers/main.yml index 397098e82ed346d037bf36e723e0c2a9a44260b2..a21681d9178fa5523531eb5196c2847706a2cc66 100644 --- a/roles/float-infra-nginx/handlers/main.yml +++ b/roles/float-infra-nginx/handlers/main.yml @@ -7,6 +7,9 @@ - name: restart sso-proxy systemd: name=sso-proxy.service state=restarted enabled=yes masked=no +- name: restart api-proxy + systemd: name=api-proxy.service state=restarted enabled=yes masked=no + - name: reload firewall systemd: name: firewall.service diff --git a/roles/float-infra-nginx/tasks/api-proxy.yml b/roles/float-infra-nginx/tasks/api-proxy.yml new file mode 100644 index 0000000000000000000000000000000000000000..6d7f27d5686a9e5c90db4ac6dce4b5edffcff9f7 --- /dev/null +++ b/roles/float-infra-nginx/tasks/api-proxy.yml @@ -0,0 +1,24 @@ +--- + +- set_fact: + api_proxy_auth_services: "{{ services.values() | selectattr('public_endpoints', 'defined') | map(attribute='public_endpoints') | flatten | selectattr('enable_api_proxy', 'defined') | selectattr('enable_api_proxy') | map(attribute='auth_service') }}" + +- name: Configure api-proxy auth services + copy: + dest: "/etc/auth-server/services.d/api-proxy.yml" + content: | + {% for s in api_proxy_auth_services %} + {{ s }}: + backends: + - backend: file + params: + src: users.yml + static_groups: [admins] + enforce_2fa: true + rate_limits: + - ip_ratelimit + - failed_login_blacklist + - anti_bruteforce_blacklist + {% endfor %} + when: "api_proxy_auth_services" + diff --git a/roles/float-infra-nginx/tasks/main.yml b/roles/float-infra-nginx/tasks/main.yml index 48b47283c5a996d01bcec7d3a82cf20e240bd527..83461947a62763183a882db9c27e15436d814700 100644 --- a/roles/float-infra-nginx/tasks/main.yml +++ b/roles/float-infra-nginx/tasks/main.yml @@ -3,3 +3,7 @@ # Only set up nginx if there are public_endpoints defined. - import_tasks: nginx.yml when: float_enable_http_frontend + +- import_tasks: api-proxy.yml + when: float_enable_http_frontend + diff --git a/roles/float-infra-nginx/tasks/nginx.yml b/roles/float-infra-nginx/tasks/nginx.yml index e4ba5f2dea85155db84193b584f37d986cb623f7..e4c3e46f2b260ae1370d9644b21c6d6b30a43002 100644 --- a/roles/float-infra-nginx/tasks/nginx.yml +++ b/roles/float-infra-nginx/tasks/nginx.yml @@ -6,6 +6,7 @@ state: present vars: packages: + - api-proxy - sso-proxy - nginx-full - libnginx-mod-http-headers-more-filter @@ -17,20 +18,32 @@ dest: /etc/default/sso-proxy notify: restart sso-proxy -- name: Configure ssoproxy +- name: Configure sso-proxy template: - src: proxy.yml.j2 + src: sso-proxy.yml.j2 dest: /etc/sso/proxy.yml owner: root group: sso-proxy mode: 0640 notify: restart sso-proxy -- name: Add user sso-proxy to credentials group +- name: Configure api-proxy + template: + src: api-proxy.yml.j2 + dest: /etc/api-proxy.yml + owner: root + group: api-proxy + mode: 0640 + notify: restart api-proxy + +- name: Add proxy users to credentials group user: - name: sso-proxy + name: "{{ item }}" groups: ssoproxy-credentials append: yes + loop: + - api-proxy + - sso-proxy - name: Enable sso-proxy systemd unit systemd: diff --git a/roles/float-infra-nginx/templates/api-proxy.yml.j2 b/roles/float-infra-nginx/templates/api-proxy.yml.j2 new file mode 100644 index 0000000000000000000000000000000000000000..78f44c7a8c060ff3743aef4986a2b421ae7ea513 --- /dev/null +++ b/roles/float-infra-nginx/templates/api-proxy.yml.j2 @@ -0,0 +1,24 @@ +--- + +backends: +{% for service in services.values() -%} +{% for endpoint in service.get('public_endpoints', []) -%} +{% if endpoint.get('enable_api_proxy') %} + - host: "{{ endpoint.name }}.{{ domain_public[0] }}" +{% if endpoint.get('scheme') == 'https' %} + tls_server_name: "{{ service.name }}.{{ domain }}" + client_tls: + cert: "/etc/credentials/x509/ssoproxy/client/cert.pem" + key: "/etc/credentials/x509/ssoproxy/client/private_key.pem" + ca: "/etc/credentials/x509/ssoproxy/ca.pem" +{% endif %} + upstream: + - {{ service.name }}.{{ domain }}:{{ endpoint.port }} + auth_service: "{{ endpoint.auth_service }}" + allowed_groups: +{% for group in endpoint.get('allowed_groups', ['admins']) %} + - {{ group }} +{% endfor %} +{% endif -%} +{% endfor -%} +{% endfor %} diff --git a/roles/float-infra-nginx/templates/nginx-upstream.j2 b/roles/float-infra-nginx/templates/nginx-upstream.j2 index 1b3e6bc984d20eef687c48d41bfecfae532b07b0..10894843f669107ba1688aae405bc982968fab3b 100644 --- a/roles/float-infra-nginx/templates/nginx-upstream.j2 +++ b/roles/float-infra-nginx/templates/nginx-upstream.j2 @@ -5,6 +5,11 @@ upstream {{ upstream.name }}{% if shard %}_{{ shard }}{% endif %} { Talk directly to the SSO proxy on localhost. #} server 127.0.0.1:5003; +{% elif upstream.enable_api_proxy | default(False) %} +{# + Talk directly to the api-proxy on localhost. +#} + server 127.0.0.1:5009; {% else %} {# Use the internal endpoint name, which resolves to multiple IP diff --git a/roles/float-infra-nginx/templates/nginx-vhost.j2 b/roles/float-infra-nginx/templates/nginx-vhost.j2 index 244cc8b201febeb7c3d2f03c5b95fd49bb0cb703..67064c5cdf91fd7727ab137aa2690826c9704200 100644 --- a/roles/float-infra-nginx/templates/nginx-vhost.j2 +++ b/roles/float-infra-nginx/templates/nginx-vhost.j2 @@ -8,14 +8,14 @@ location {{ pe_config.path }} { include /etc/nginx/snippets/block.conf; include /etc/nginx/snippets/proxy.conf; -{% if not upstream.enable_sso_proxy and pe_config.get('scheme', 'https') == 'https' %} +{% if not upstream.enable_sso_proxy and not upstream.enable_api_proxy and pe_config.get('scheme', 'https') == 'https' %} proxy_pass https://{{ pe_config.float_upstream_name }}{% if upstream.sharded and shard %}_{{ shard }}{% endif %}; include /etc/nginx/snippets/proxy-ssl.conf; proxy_ssl_name {{ upstream.service_name }}.{{ domain }}; {% else %} proxy_pass http://{{ pe_config.float_upstream_name }}{% if upstream.sharded and shard %}_{{ shard }}{% endif %}; {% endif %} -{% if not upstream.enable_sso_proxy %} +{% if not upstream.enable_sso_proxy and not upstream.enable_api_proxy %} proxy_cache global; {% endif %} } diff --git a/roles/float-infra-nginx/templates/proxy.yml.j2 b/roles/float-infra-nginx/templates/sso-proxy.yml.j2 similarity index 100% rename from roles/float-infra-nginx/templates/proxy.yml.j2 rename to roles/float-infra-nginx/templates/sso-proxy.yml.j2