Skip to content

Commit 70c5196

Browse files
authored
Merge pull request #228 from ESGF/ansible-auth-config
Added Ansible config for auth components (allowing access control)
2 parents fc9a149 + 0887913 commit 70c5196

File tree

9 files changed

+329
-9
lines changed

9 files changed

+329
-9
lines changed

deploy/ansible/host_vars/esgf.data.example.org

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,55 @@
3434

3535
### See: https://github.com/ESGF/esgf-docker/blob/master/docs/deploy-ansible.md#enabling-ssl
3636

37+
38+
## OPTIONAL: Enabling access control components
39+
40+
#auth_enabled: true/false
41+
42+
## Configuration for the auth service container
43+
#auth_settings:
44+
# MIDDLEWARE:
45+
# - authenticate.oauth2.middleware.BearerTokenAuthenticationMiddleware
46+
# - authenticate.oidc.middleware.OpenIDConnectAuthenticationMiddleware
47+
# - authorize.opa.middleware.OPAAuthorizationMiddleware
48+
# OPA_SERVER:
49+
# package_path: esgf
50+
# rule_name: allow
51+
# # Group info keys for authorization
52+
# OAUTH2_GROUPS_KEY: group_membership
53+
# OIDC_GROUPS_KEY: group_membership
54+
# # OAuth Bearer Token auth settings
55+
# OAUTH_CLIENT_ID:
56+
# OAUTH_CLIENT_SECRET:
57+
# OAUTH_TOKEN_URL:
58+
# OAUTH_TOKEN_INTROSPECT_URL:
59+
# # OIDC auth settings
60+
# OIDC_BACKEND_CLIENT_NAME: esgf
61+
# AUTHLIB_OAUTH_CLIENTS:
62+
# esgf:
63+
# client_id:
64+
# client_secret:
65+
# authorize_url:
66+
# userinfo_endpoint:
67+
# client_kwargs:
68+
# scope: openid profile email
69+
70+
## Default rego template (override this to use your own).
71+
#opa_policy_template: policy.rego.j2
72+
73+
## Paths to apply the authorisation policy to and the access group that a user will need.
74+
#opa_policy_restricted_paths:
75+
# - name: threddsdata
76+
# path: /thredds/fileServer/restricted/.*
77+
# group: admins
78+
# - name: example
79+
# path: /some/restricted/path/.*
80+
# group: admins
81+
82+
## Your server's name. The default policy will deny requests from other hostnames.
83+
#opa_policy_server_host: example.com
84+
85+
## The logging level of the OPA container. Set this to debug for troubleshooting.
86+
#opa_log_level: info
87+
88+
### See: https://github.com/ESGF/esgf-docker/blob/master/docs/deploy-ansible.md#enabling-access-control

deploy/ansible/roles/auth/defaults/main.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,38 @@ image_pull: true
2121
# Indicates if the auth service should be deployed or not
2222
auth_enabled: false
2323

24+
auth_settings_base:
25+
MIDDLEWARE:
26+
- django.middleware.security.SecurityMiddleware
27+
- django.contrib.sessions.middleware.SessionMiddleware
28+
- django.middleware.common.CommonMiddleware
29+
- django.middleware.csrf.CsrfViewMiddleware
30+
- django.contrib.messages.middleware.MessageMiddleware
31+
- django.middleware.clickjacking.XFrameOptionsMiddleware
32+
OPA_SERVER:
33+
host: opa
34+
port: 8181
35+
RESOURCE_URI_QUERY_KEY: rd
36+
RESOURCE_URI_HEADER_KEY: HTTP_X_ORIGINAL_URL
37+
38+
# Default rego template (override this to use your own)
39+
opa_policy_template: policy.rego.j2
40+
41+
# Paths to apply security restrictions to
42+
opa_policy_restricted_paths: []
43+
44+
# Whitelist access based on the server's hostname
45+
opa_policy_server_host: "{{ ansible_host }}"
46+
47+
# Logging level for the OPA server
48+
opa_log_level: info
49+
50+
# Settings for the opa image
51+
opa_image_prefix: "{{ image_prefix }}"
52+
opa_image_tag: "{{ image_tag }}"
53+
opa_image_pull: "{{ image_pull }}"
54+
opa_image_repository: opa
55+
2456
# Settings for the auth-service image
2557
auth_image_prefix: "{{ image_prefix }}"
2658
auth_image_tag: "{{ image_tag }}"

deploy/ansible/roles/auth/tasks/auth_install.yml

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,47 @@
44
docker_network:
55
name: esgf
66

7-
- name: Start auth container
7+
- name: Make auth config directory
8+
file:
9+
path: /esg/config/auth
10+
state: directory
11+
12+
- name: Write rego policy file
13+
template:
14+
src: "{{ opa_policy_template }}"
15+
dest: /esg/config/auth/policy.rego
16+
17+
- name: Write auth service settings
18+
template:
19+
src: settings.yaml.j2
20+
dest: /esg/config/auth/settings.yaml
21+
22+
- name: Start the opa container
23+
docker_container:
24+
name: opa
25+
image: "{{ opa_image_prefix }}/{{ opa_image_repository }}:{{ opa_image_tag }}"
26+
pull: "{{ opa_image_pull }}"
27+
detach: yes
28+
restart_policy: unless-stopped
29+
exposed_ports:
30+
- "8181"
31+
networks:
32+
- name: esgf
33+
networks_cli_compatible: yes
34+
volumes:
35+
# Mount the policy for the opa server
36+
- "/esg/config/auth/policy.rego:/policies/policy.rego:ro"
37+
entrypoint:
38+
- "/opa"
39+
- "run"
40+
- "--ignore=.*" # exclude hidden dirs created by Kubernetes
41+
- "--log-level={{ opa_log_level }}"
42+
- "--server"
43+
- "/policies"
44+
state: started
45+
recreate: yes
46+
47+
- name: Start auth service container
848
docker_container:
949
name: auth
1050
image: "{{ auth_image_prefix }}/{{ auth_image_repository }}:{{ auth_image_tag }}"
@@ -15,3 +55,11 @@
1555
- "8080"
1656
networks:
1757
- name: esgf
58+
networks_cli_compatible: yes
59+
volumes:
60+
# Mount the settings for the auth service
61+
- "/esg/config/auth/settings.yaml:/etc/django/settings.d/20-runtime-settings.yaml:ro"
62+
# Mount the settings for the auth service
63+
- "/esg/config/auth/staticfiles:/var/django/staticfiles:ro"
64+
state: started
65+
recreate: yes

deploy/ansible/roles/auth/tasks/auth_uninstall.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
---
22

3-
- name: Stop auth container
3+
- name: Stop opa container
4+
docker_container:
5+
name: opa
6+
state: absent
7+
8+
- name: Stop auth service container
49
docker_container:
510
name: auth
611
state: absent
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package esgf
2+
3+
default allow = false
4+
5+
# Determine access to the resource
6+
allow = true {
7+
allowed_hosts[resource_host]
8+
count(violation) == 0
9+
}
10+
11+
# Check that the user belongs to a certain group
12+
has_group(name) {
13+
some i
14+
input.subject.groups[i] == name
15+
}
16+
17+
# Separate parts of a resource URL, if applicable
18+
parts := regex.find_all_string_submatch_n("^(?:(?:http|https|ftp):\/\/([^\/ ]*))?(\/.*)", input.resource, -1)
19+
resource_host := parts[_][1]
20+
resource_path := parts[_][2]
21+
22+
# Declare all allowed resource hosts
23+
allowed_hosts := {
24+
"{{ ansible_host }}",
25+
}
26+
27+
# Check requested path against restricted paths
28+
{% for restricted_path in opa_policy_restricted_paths %}
29+
violation["{{ restricted_path['name'] }}"] {
30+
regex.match("{{ restricted_path['path'] }}", resource_path)
31+
not has_group("{{ restricted_path['group'] }}")
32+
}
33+
{% endfor %}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Generated by Ansible
2+
3+
{{ auth_settings_base | combine(auth_settings, recursive=true, list_merge='append_rp') | to_yaml(indent=2) }}

deploy/ansible/roles/proxy/templates/proxy.conf.j2

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
{% macro auth_check() -%}
2+
error_page 401 = @error401;
3+
auth_request /verify;
4+
auth_request_set $username $upstream_http_x_username;
5+
auth_request_set $sid $upstream_http_x_session;
6+
{%- endmacro %}
7+
18
# HTTP and HTTPS server blocks that proxy to the other containers running on this host
29

310
server {
@@ -41,7 +48,7 @@ server {
4148
}
4249

4350
location @error401 {
44-
return 302 /login;
51+
return 302 http://$host/login;
4552
}
4653
{% endif %}
4754

@@ -51,19 +58,16 @@ server {
5158
include /etc/nginx/includes/proxy_params.conf;
5259
proxy_pass http://thredds:8080;
5360

54-
{% if auth_enabled %}
55-
error_page 401 = @error401;
56-
auth_request /auth/verify;
57-
auth_request_set $username $upstream_http_x_username;
58-
auth_request_set $sid $upstream_http_x_session;
59-
{% endif %}
61+
{% if auth_enabled %}{{ auth_check() }}{% endif %}
6062
}
6163
{% endif %}
6264

6365
{% if fileserver_enabled %}
6466
location /thredds/fileServer {
6567
include /etc/nginx/includes/proxy_params.conf;
6668
proxy_pass http://fileserver:8080;
69+
70+
{% if auth_enabled %}{{ auth_check() }}{% endif %}
6771
}
6872
{% endif %}
6973
{% endif %}
@@ -74,13 +78,17 @@ server {
7478
location ~ ^/solr/[a-z]+/replication {
7579
include /etc/nginx/includes/proxy_params.conf;
7680
proxy_pass http://solr-slave:8983;
81+
82+
{% if auth_enabled %}{{ auth_check() }}{% endif %}
7783
}
7884
{% endif %}
7985

8086
{% if search_enabled %}
8187
location /esg-search {
8288
include /etc/nginx/includes/proxy_params.conf;
8389
proxy_pass http://search:8080;
90+
91+
{% if auth_enabled %}{{ auth_check() }}{% endif %}
8492
}
8593
{% endif %}
8694
{% endif %}

deploy/ansible/roles/proxy/templates/ssl.proxy.conf.j2

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
{% macro auth_check() -%}
2+
error_page 401 = @error401;
3+
auth_request /verify;
4+
auth_request_set $username $upstream_http_x_username;
5+
auth_request_set $sid $upstream_http_x_session;
6+
{%- endmacro %}
7+
18
# HTTP and HTTPS server blocks that proxy to the other containers running on this host
29
server {
310
listen 8080 default_server;
@@ -25,18 +32,55 @@ server {
2532
return 404;
2633
}
2734

35+
{% if auth_enabled %}
36+
location /verify {
37+
set $query '';
38+
if ($request_uri ~* "[^\?]+\?(.*)$") {
39+
set $query $1;
40+
}
41+
42+
proxy_pass http://auth:8080/verify/?next=$scheme://$http_host$http_port$request_uri;
43+
proxy_pass_request_body off;
44+
45+
proxy_set_header Content-Length '0';
46+
proxy_set_header Host $host;
47+
proxy_set_header X-Origin-URI $request_uri;
48+
proxy_set_header X-Origin-Query $query;
49+
proxy_set_header X-Forwarded-Host $host;
50+
}
51+
52+
location /login {
53+
proxy_pass http://auth:8080/login;
54+
proxy_pass_request_body off;
55+
56+
proxy_set_header Content-Length '0';
57+
proxy_set_header Host $host;
58+
proxy_set_header X-Origin-URI $request_uri;
59+
proxy_set_header X-Origin-Query $query;
60+
proxy_set_header X-Forwarded-Host $host;
61+
}
62+
63+
location @error401 {
64+
return 302 https://$host/login;
65+
}
66+
{% endif %}
67+
2868
{% if 'data' in group_names %}
2969
{% if thredds_enabled %}
3070
location /thredds {
3171
include /etc/nginx/includes/proxy_params.conf;
3272
proxy_pass http://thredds:8080;
73+
74+
{% if auth_enabled %}{{ auth_check() }}{% endif %}
3375
}
3476
{% endif %}
3577

3678
{% if fileserver_enabled %}
3779
location /thredds/fileServer {
3880
include /etc/nginx/includes/proxy_params.conf;
3981
proxy_pass http://fileserver:8080;
82+
83+
{% if auth_enabled %}{{ auth_check() }}{% endif %}
4084
}
4185
{% endif %}
4286
{% endif %}
@@ -47,13 +91,17 @@ server {
4791
location ~ ^/solr/[a-z]+/replication {
4892
include /etc/nginx/includes/proxy_params.conf;
4993
proxy_pass http://solr-slave:8983;
94+
95+
{% if auth_enabled %}{{ auth_check() }}{% endif %}
5096
}
5197
{% endif %}
5298

5399
{% if search_enabled %}
54100
location /esg-search {
55101
include /etc/nginx/includes/proxy_params.conf;
56102
proxy_pass http://search:8080;
103+
104+
{% if auth_enabled %}{{ auth_check() }}{% endif %}
57105
}
58106
{% endif %}
59107
{% endif %}

0 commit comments

Comments
 (0)