From 288482d5b6f9d84665197ce7b9947266a797e1d0 Mon Sep 17 00:00:00 2001 From: dcore94 Date: Tue, 18 May 2021 15:10:08 +0200 Subject: [PATCH] conductor 3.0.4 with oauth2 and pep --- group_vars/nw_cluster.yaml | 3 - group_vars/pre_cluster.yaml | 3 - group_vars/prod_cluster.yaml | 3 - inventory/hosts.dev | 6 +- inventory/hosts.nw_cluster | 5 + local-site/base-config.cfg | 14 - local-site/conductor-db-init.sql | 188 ----------- local-site/conductor-swarm-config.properties | 42 --- local-site/conductor-workers-swarm.yaml | 32 -- local-site/conductor.yaml | 144 --------- local-site/keycloak.js | 20 -- local-site/nginx.conf | 22 -- local-site/nginx.default.conf | 78 ----- local-site/nginx.env | 4 - local-site/oauth2auth.cfg | 24 -- local-site/pep.cfg | 28 -- roles/cluster-replacement/defaults/main.yml | 56 ---- roles/cluster-replacement/tasks/main.yml | 16 - .../templates/haproxy-docker-swarm.yaml.j2 | 56 ---- .../templates/haproxy.cfg.j2 | 75 ----- roles/cluster-replacement/vars/main.yml | 2 - roles/common/defaults/main.yaml | 1 + .../defaults/conductor_ui_secrets.yaml | 15 + roles/conductor/defaults/main.yaml | 9 +- roles/conductor/tasks/main.yaml | 20 +- roles/conductor/templates/auth.cfg.j2 | 23 -- .../conductor-swarm-config.properties.j2 | 99 ++---- .../templates/conductor-swarm.yaml.j2 | 40 +-- .../conductor/templates/local_auth.cfg.j2 | 0 roles/conductor/templates/oauth2_auth.cfg.j2 | 24 ++ .../templates/elasticsearch-swarm.yaml.j2 | 5 +- roles/databases/templates/mysql-swarm.yaml.j2 | 12 +- .../templates/postgres-swarm.yaml.j2 | 15 +- .../templates/elasticsearch-swarm.yaml.j2 | 6 +- roles/mysql/defaults/main.yml | 10 - roles/mysql/tasks/main.yaml | 5 - roles/mysql/templates/mysql-swarm.yaml.j2 | 30 -- roles/pep/defaults/main.yaml | 2 + roles/pep/defaults/pep_credentials.yaml | 18 ++ roles/pep/tasks/main.yaml | 34 ++ roles/pep/templates/config.js.j2 | 98 ++++++ roles/pep/templates/nginx.conf.j2 | 18 ++ roles/pep/templates/nginx.default.conf.j2 | 96 ++++++ .../pep/templates/nginx.default.conf.nopep.j2 | 40 +++ roles/pep/templates/pep-swarm.yaml.j2 | 35 ++ roles/pep/templates/pep.js.j2 | 299 ++++++++++++++++++ roles/postgres/defaults/main.yml | 9 - roles/postgres/tasks/main.yaml | 5 - .../postgres/templates/postgres-swarm.yaml.j2 | 31 -- roles/workers/defaults/main.yaml | 3 +- .../templates/conductor-workers-swarm.yaml.j2 | 3 +- roles/workers/templates/config.cfg.j2 | 4 +- site-dev.yaml | 21 +- site-local.yaml | 52 +++ site-nw-cluster.yaml | 61 ++++ site-pre.yaml | 33 +- site-prod.yaml | 33 +- site.yaml | 56 ---- 58 files changed, 920 insertions(+), 1166 deletions(-) delete mode 100644 group_vars/nw_cluster.yaml delete mode 100644 group_vars/pre_cluster.yaml delete mode 100644 group_vars/prod_cluster.yaml create mode 100644 inventory/hosts.nw_cluster delete mode 100644 local-site/base-config.cfg delete mode 100644 local-site/conductor-db-init.sql delete mode 100644 local-site/conductor-swarm-config.properties delete mode 100644 local-site/conductor-workers-swarm.yaml delete mode 100644 local-site/conductor.yaml delete mode 100644 local-site/keycloak.js delete mode 100644 local-site/nginx.conf delete mode 100644 local-site/nginx.default.conf delete mode 100644 local-site/nginx.env delete mode 100644 local-site/oauth2auth.cfg delete mode 100644 local-site/pep.cfg delete mode 100644 roles/cluster-replacement/defaults/main.yml delete mode 100644 roles/cluster-replacement/tasks/main.yml delete mode 100644 roles/cluster-replacement/templates/haproxy-docker-swarm.yaml.j2 delete mode 100644 roles/cluster-replacement/templates/haproxy.cfg.j2 delete mode 100644 roles/cluster-replacement/vars/main.yml create mode 100644 roles/conductor/defaults/conductor_ui_secrets.yaml delete mode 100644 roles/conductor/templates/auth.cfg.j2 rename local-site/auth.cfg => roles/conductor/templates/local_auth.cfg.j2 (100%) create mode 100644 roles/conductor/templates/oauth2_auth.cfg.j2 delete mode 100644 roles/mysql/defaults/main.yml delete mode 100644 roles/mysql/tasks/main.yaml delete mode 100644 roles/mysql/templates/mysql-swarm.yaml.j2 create mode 100644 roles/pep/defaults/main.yaml create mode 100644 roles/pep/defaults/pep_credentials.yaml create mode 100644 roles/pep/tasks/main.yaml create mode 100644 roles/pep/templates/config.js.j2 create mode 100644 roles/pep/templates/nginx.conf.j2 create mode 100644 roles/pep/templates/nginx.default.conf.j2 create mode 100644 roles/pep/templates/nginx.default.conf.nopep.j2 create mode 100644 roles/pep/templates/pep-swarm.yaml.j2 create mode 100644 roles/pep/templates/pep.js.j2 delete mode 100644 roles/postgres/defaults/main.yml delete mode 100644 roles/postgres/tasks/main.yaml delete mode 100644 roles/postgres/templates/postgres-swarm.yaml.j2 create mode 100644 site-local.yaml create mode 100644 site-nw-cluster.yaml delete mode 100644 site.yaml diff --git a/group_vars/nw_cluster.yaml b/group_vars/nw_cluster.yaml deleted file mode 100644 index 7135987..0000000 --- a/group_vars/nw_cluster.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -infrastructure: dev -conductor_workers_server: http://conductor-dev.int.d4science.net/api diff --git a/group_vars/pre_cluster.yaml b/group_vars/pre_cluster.yaml deleted file mode 100644 index b982dd1..0000000 --- a/group_vars/pre_cluster.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -infrastructure: pre -conductor_workers_server: https://conductor.pre.d4science.org/api diff --git a/group_vars/prod_cluster.yaml b/group_vars/prod_cluster.yaml deleted file mode 100644 index 1cb1f83..0000000 --- a/group_vars/prod_cluster.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -infrastructure: prod -conductor_workers_server: https://conductor.d4science.org/api diff --git a/inventory/hosts.dev b/inventory/hosts.dev index e48107d..f50e1e5 100644 --- a/inventory/hosts.dev +++ b/inventory/hosts.dev @@ -1,5 +1,5 @@ [dev_infra:children] -nw_cluster +dev_cluster -[nw_cluster] -nubis1.int.d4science.net +[dev_cluster] +conductor.dev.d4science.org diff --git a/inventory/hosts.nw_cluster b/inventory/hosts.nw_cluster new file mode 100644 index 0000000..44c1b89 --- /dev/null +++ b/inventory/hosts.nw_cluster @@ -0,0 +1,5 @@ +[nw_cluster_infra:children] +nw_cluster + +[nw_cluster] +nubis1.int.d4science.net diff --git a/local-site/base-config.cfg b/local-site/base-config.cfg deleted file mode 100644 index fa069a4..0000000 --- a/local-site/base-config.cfg +++ /dev/null @@ -1,14 +0,0 @@ -[common] -loglevel = info -server = http://conductor-server:8080/api -threads = 1 -pollrate = 1 - -[pymail] -server=smtp-relay.d4science.org -user=conductor_dev -password=d20d6ea975b01bc -protocol=starttls -port=587 - - diff --git a/local-site/conductor-db-init.sql b/local-site/conductor-db-init.sql deleted file mode 100644 index 7c3dba9..0000000 --- a/local-site/conductor-db-init.sql +++ /dev/null @@ -1,188 +0,0 @@ --- V1__initial_schema.sql-- --- -------------------------------------------------------------------------------------------------------------- --- SCHEMA FOR METADATA DAO --- -------------------------------------------------------------------------------------------------------------- - -CREATE TABLE meta_event_handler ( - id SERIAL, - created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - name varchar(255) NOT NULL, - event varchar(255) NOT NULL, - active boolean NOT NULL, - json_data TEXT NOT NULL, - PRIMARY KEY (id) -); -CREATE INDEX event_handler_name_index ON meta_event_handler (name); -CREATE INDEX event_handler_event_index ON meta_event_handler (event); - -CREATE TABLE meta_task_def ( - id SERIAL, - created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - name varchar(255) NOT NULL, - json_data TEXT NOT NULL, - PRIMARY KEY (id) -); -CREATE UNIQUE INDEX unique_task_def_name ON meta_task_def (name); - -CREATE TABLE meta_workflow_def ( - id SERIAL, - created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - name varchar(255) NOT NULL, - version int NOT NULL, - latest_version int NOT NULL DEFAULT 0, - json_data TEXT NOT NULL, - PRIMARY KEY (id) -); -CREATE UNIQUE INDEX unique_name_version ON meta_workflow_def (name,version); -CREATE INDEX workflow_def_name_index ON meta_workflow_def (name); - --- -------------------------------------------------------------------------------------------------------------- --- SCHEMA FOR EXECUTION DAO --- -------------------------------------------------------------------------------------------------------------- - -CREATE TABLE event_execution ( - id SERIAL, - created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - event_handler_name varchar(255) NOT NULL, - event_name varchar(255) NOT NULL, - message_id varchar(255) NOT NULL, - execution_id varchar(255) NOT NULL, - json_data TEXT NOT NULL, - PRIMARY KEY (id) -); -CREATE UNIQUE INDEX unique_event_execution ON event_execution (event_handler_name,event_name,message_id); - -CREATE TABLE poll_data ( - id SERIAL, - created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - queue_name varchar(255) NOT NULL, - domain varchar(255) NOT NULL, - json_data TEXT NOT NULL, - PRIMARY KEY (id) -); -CREATE UNIQUE INDEX unique_poll_data ON poll_data (queue_name,domain); -CREATE INDEX ON poll_data (queue_name); - -CREATE TABLE task_scheduled ( - id SERIAL, - created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - workflow_id varchar(255) NOT NULL, - task_key varchar(255) NOT NULL, - task_id varchar(255) NOT NULL, - PRIMARY KEY (id) -); -CREATE UNIQUE INDEX unique_workflow_id_task_key ON task_scheduled (workflow_id,task_key); - -CREATE TABLE task_in_progress ( - id SERIAL, - created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - task_def_name varchar(255) NOT NULL, - task_id varchar(255) NOT NULL, - workflow_id varchar(255) NOT NULL, - in_progress_status boolean NOT NULL DEFAULT false, - PRIMARY KEY (id) -); -CREATE UNIQUE INDEX unique_task_def_task_id1 ON task_in_progress (task_def_name,task_id); - -CREATE TABLE task ( - id SERIAL, - created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - task_id varchar(255) NOT NULL, - json_data TEXT NOT NULL, - PRIMARY KEY (id) -); -CREATE UNIQUE INDEX unique_task_id ON task (task_id); - -CREATE TABLE workflow ( - id SERIAL, - created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - workflow_id varchar(255) NOT NULL, - correlation_id varchar(255), - json_data TEXT NOT NULL, - PRIMARY KEY (id) -); -CREATE UNIQUE INDEX unique_workflow_id ON workflow (workflow_id); - -CREATE TABLE workflow_def_to_workflow ( - id SERIAL, - created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - workflow_def varchar(255) NOT NULL, - date_str varchar(60), - workflow_id varchar(255) NOT NULL, - PRIMARY KEY (id) -); -CREATE UNIQUE INDEX unique_workflow_def_date_str ON workflow_def_to_workflow (workflow_def,date_str,workflow_id); - -CREATE TABLE workflow_pending ( - id SERIAL, - created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - workflow_type varchar(255) NOT NULL, - workflow_id varchar(255) NOT NULL, - PRIMARY KEY (id) -); -CREATE UNIQUE INDEX unique_workflow_type_workflow_id ON workflow_pending (workflow_type,workflow_id); -CREATE INDEX workflow_type_index ON workflow_pending (workflow_type); - -CREATE TABLE workflow_to_task ( - id SERIAL, - created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - modified_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - workflow_id varchar(255) NOT NULL, - task_id varchar(255) NOT NULL, - PRIMARY KEY (id) -); -CREATE UNIQUE INDEX unique_workflow_to_task_id ON workflow_to_task (workflow_id,task_id); -CREATE INDEX workflow_id_index ON workflow_to_task (workflow_id); - --- -------------------------------------------------------------------------------------------------------------- --- SCHEMA FOR QUEUE DAO --- -------------------------------------------------------------------------------------------------------------- - -CREATE TABLE queue ( - id SERIAL, - created_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - queue_name varchar(255) NOT NULL, - PRIMARY KEY (id) -); -CREATE UNIQUE INDEX unique_queue_name ON queue (queue_name); - -CREATE TABLE queue_message ( - id SERIAL, - created_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - deliver_on TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - queue_name varchar(255) NOT NULL, - message_id varchar(255) NOT NULL, - priority integer DEFAULT 0, - popped boolean DEFAULT false, - offset_time_seconds BIGINT, - payload TEXT, - PRIMARY KEY (id) -); -CREATE UNIQUE INDEX unique_queue_name_message_id ON queue_message (queue_name,message_id); -CREATE INDEX combo_queue_message ON queue_message (queue_name,popped,deliver_on,created_on); - --- V2__1009_Fix_PostgresExecutionDAO_Index.sql -- -DROP INDEX IF EXISTS unique_event_execution; - -CREATE UNIQUE INDEX unique_event_execution ON event_execution (event_handler_name,event_name,execution_id); - --- V3__correlation_id_index.sql -- -DROP INDEX IF EXISTS workflow_corr_id_index; - -CREATE INDEX workflow_corr_id_index ON workflow (correlation_id); - --- V4__new_qm_index_with_priority.sql -- -DROP INDEX IF EXISTS combo_queue_message; - -CREATE INDEX combo_queue_message ON queue_message (queue_name,priority,popped,deliver_on,created_on); diff --git a/local-site/conductor-swarm-config.properties b/local-site/conductor-swarm-config.properties deleted file mode 100644 index 474f78b..0000000 --- a/local-site/conductor-swarm-config.properties +++ /dev/null @@ -1,42 +0,0 @@ -# Servers. -conductor.jetty.server.enabled=true -conductor.grpc.server.enabled=false - -# Database persistence model. Possible values are memory, redis, and dynomite. -# If ommitted, the persistence used is memory -# -# memory : The data is stored in memory and lost when the server dies. Useful for testing or demo -# redis : non-Dynomite based redis instance -# dynomite : Dynomite cluster. Use this for HA configuration. -db=postgres -jdbc.url=jdbc:postgresql://postgresdb:5432/conductor -jdbc.username=conductor -jdbc.password=password -conductor.postgres.connection.pool.size.max=10 -conductor.postgres.connection.pool.idle.min=2 -flyway.enabled=false - - -# Elastic search instance type. Possible values are memory and external. -# If not specified, the instance type will be embedded in memory -# -# memory: The instance is created in memory and lost when the server dies. Useful for development and testing. -# external: Elastic search instance runs outside of the server. Data is persisted and does not get lost when -# the server dies. Useful for more stable environments like staging or production. -workflow.elasticsearch.instanceType=external - -# Transport address to elasticsearch -workflow.elasticsearch.url=elasticsearch:9300 - -# Name of the elasticsearch cluster -workflow.elasticsearch.index.name=conductor - -# Additional modules (optional) -# conductor.additional.modules=class_extending_com.google.inject.AbstractModule - -# Additional modules for metrics collection (optional) -# conductor.additional.modules=com.netflix.conductor.contribs.metrics.MetricsRegistryModule,com.netflix.conductor.contribs.metrics.LoggingMetricsModule -# com.netflix.conductor.contribs.metrics.LoggingMetricsModule.reportPeriodSeconds=15 - -# Load sample kitchen sink workflow -loadSample=false diff --git a/local-site/conductor-workers-swarm.yaml b/local-site/conductor-workers-swarm.yaml deleted file mode 100644 index 6cfdb70..0000000 --- a/local-site/conductor-workers-swarm.yaml +++ /dev/null @@ -1,32 +0,0 @@ -version: '3.6' - -services: - base: - environment: - CONDUCTOR_SERVER: http://conductor-dev.int.d4science.net/api - configs: - - source: base-config - target: /app/config.cfg - image: 'nubisware/nubisware-conductor-worker-py-base' - networks: - - conductor-network - deploy: - mode: replicated - replicas: 2 - placement: - constraints: [node.role == worker] - restart_policy: - condition: on-failure - delay: 5s - max_attempts: 3 - window: 120s - logging: - driver: "journald" - - -networks: - conductor-network: - -configs: - base-config: - file: base-config.cfg diff --git a/local-site/conductor.yaml b/local-site/conductor.yaml deleted file mode 100644 index 3f99f74..0000000 --- a/local-site/conductor.yaml +++ /dev/null @@ -1,144 +0,0 @@ -version: '3.6' - - -services: - postgresdb: - image: postgres - environment: - POSTGRES_USER: "conductor" - POSTGRES_PASSWORD: "password" - POSTGRES_DB: "conductor" - configs: - - source: db-init - target: "/docker-entrypoint-initdb.d/db-init.sql" - networks: - - conductor-network - deploy: - replicas: 1 - - conductor-server: - environment: - - CONFIG_PROP=conductor-swarm-config.properties - image: nubisware/conductor-server - networks: - - conductor-network - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: on-failure - delay: 15s - max_attempts: 10 - window: 120s - configs: - - source: swarm-config - target: /app/config/conductor-swarm-config.properties - -# logging: -# driver: "journald" - - elasticsearch: - image: docker.elastic.co/elasticsearch/elasticsearch:5.6.8 - environment: - - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - - transport.host=0.0.0.0 - - discovery.type=single-node - - xpack.security.enabled=false - networks: - conductor-network: - aliases: - - es -# logging: -# driver: "journald" - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: on-failure - delay: 5s - max_attempts: 3 - window: 120s - - conductor-ui: - environment: - - WF_SERVER=http://conductor-server:8080/api/ - - AUTH_CONFIG_PATH=/app/config/auth.config - #image: nubisware/conductor-ui - #image: nubisware/conductor-ui_oauth2:2.31 - image: conductor-ui_oauth2:2.31 - networks: - - conductor-network - configs: - - source: auth-config - target: /app/config/auth.config - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: on-failure - delay: 15s - max_attempts: 10 - window: 120s - - base: - environment: - CONDUCTOR_SERVER: http://conductor-dev.int.d4science.net/api - configs: - - source: base-config - target: /app/config.cfg - image: 'nubisware/nubisware-conductor-worker-py-base' - networks: - - conductor-network - deploy: - mode: replicated - replicas: 1 - restart_policy: - condition: on-failure - delay: 5s - max_attempts: 3 - window: 120s -# logging: -# driver: "journald" - - pep: - image: nginx:1.19.8-alpine - networks: - - conductor-network - ports: - - "80:80" - env_file: - - nginx.env - volumes: - - "${PWD}/keycloak.js:/etc/nginx/keycloak.js" - - # to be uncommented for debug porposes - #command: [nginx-debug, '-g', 'daemon off;'] - deploy: - replicas: 1 - restart_policy: - condition: on-failure - delay: 10s - # max_attempts: 3 - window: 120s - configs: - - source: nginxconf - target: /etc/nginx/templates/default.conf.template - - source: nginxbaseconf - target: /etc/nginx/nginx.conf - -networks: - conductor-network: - -configs: - swarm-config: - file: ./conductor-swarm-config.properties - auth-config: - file: ./oauth2auth.cfg - db-init: - file: ./conductor-db-init.sql - base-config: - file: base-config.cfg - nginxconf: - file: ${PWD}/nginx.default.conf - nginxbaseconf: - file: ${PWD}/nginx.conf diff --git a/local-site/keycloak.js b/local-site/keycloak.js deleted file mode 100644 index f31e214..0000000 --- a/local-site/keycloak.js +++ /dev/null @@ -1,20 +0,0 @@ -export default { introspectAccessToken }; - -function introspectAccessToken(r) { - r.error("Inside introspectAccessToken " + njs.dump(r.variables)) - r.subrequest("/jwt_verify_request", - function(reply) { - if (reply.status == 200) { - var response = JSON.parse(reply.responseBody); - r.error("Response is " + reply.responseBody) - if (response.active == true) { - r.return(204); // Token is valid, return success code - } else { - r.return(403); // Token is invalid, return forbidden code - } - } else { - r.return(401); // Unexpected response, return 'auth required' - } - } - ); -} diff --git a/local-site/nginx.conf b/local-site/nginx.conf deleted file mode 100644 index bdc08c9..0000000 --- a/local-site/nginx.conf +++ /dev/null @@ -1,22 +0,0 @@ -load_module modules/ngx_http_js_module.so; - -worker_processes 1; - -events { - worker_connections 1024; -} - -http { - - js_import keycloak.js; - proxy_cache_path /var/cache/nginx/keycloak keys_zone=token_responses:1m max_size=2m; - -# js_import json_log.js; -# js_set $json_debug_log json_log.debugLog; -# log_format access_debug escape=none $json_debug_log; # Offload to njs -# access_log /var/log/nginx/access.log access_debug; - - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/sites-enabled/*; -} - diff --git a/local-site/nginx.default.conf b/local-site/nginx.default.conf deleted file mode 100644 index 73e8463..0000000 --- a/local-site/nginx.default.conf +++ /dev/null @@ -1,78 +0,0 @@ -upstream _conductor-server { - ip_hash; - server conductor-server:8080; -} - -upstream _conductor-ui { - ip_hash; - server conductor-ui:5000; -} - -map $http_authorization $source_token { - default ""; - "~*^Bearer\s+(?[\S]+)$" $token; -} - -server { - - listen *:80; - listen [::]:80; - server_name conductor-server; - - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Server $host; - proxy_set_header X-Forwarded-Port $server_port; - proxy_set_header X-Forwarded-Proto $scheme; - - location / { - #auth_request /jwt_verify; - proxy_pass http://_conductor-server; - } - - location = /jwt_verify { - internal; - js_content keycloak.introspectAccessToken; - } - - location /jwt_verify_request { - internal; - proxy_method POST; - proxy_http_version 1.1; - proxy_set_header Host "127.0.0.1"; - proxy_set_header Authorization "Basic Z2F5YV9wZXA6NWJiN2RjYWItN2NlNy00YTQ3LTlmNTUtZmE4MWFlYmNjM2I4"; - proxy_set_header Content-Type "application/x-www-form-urlencoded"; - proxy_set_body "token=$source_token&token_type_hint=access_token"; - proxy_pass http://accounts.dev.d4science.org/auth/realms/master/protocol/openid-connect/token/introspect; - - proxy_cache token_responses; # Enable caching - proxy_cache_key $source_token; # Cache for each access token - proxy_cache_lock on; # Duplicate tokens must wait - proxy_cache_valid 200 10s; # How long to use each response - proxy_ignore_headers Cache-Control Expires Set-Cookie; - } - -} - -server { - - listen *:80 default_server; - listen [::]:80 default_server; - server_name conductor-ui; - - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Server $host; - proxy_set_header X-Forwarded-Port $server_port; - proxy_set_header X-Forwarded-Proto $scheme; - - location / { - #auth_request /jwt_verify; - proxy_pass http://_conductor-ui; - } - -} diff --git a/local-site/nginx.env b/local-site/nginx.env deleted file mode 100644 index 6dc36a1..0000000 --- a/local-site/nginx.env +++ /dev/null @@ -1,4 +0,0 @@ -NGINX_PORT=80 -CAMUNDA_PORT=8080 -PGADMIN_PORT=80 -KEYCLOAK_PORT=8080 diff --git a/local-site/oauth2auth.cfg b/local-site/oauth2auth.cfg deleted file mode 100644 index 6d522ab..0000000 --- a/local-site/oauth2auth.cfg +++ /dev/null @@ -1,24 +0,0 @@ -{ - "strategy": "oauth2", - "strategySettings": { - "authorizationURL": "https://accounts.dev.d4science.org/auth/realms/d4science/protocol/openid-connect/auth", - "tokenURL": "https://accounts.dev.d4science.org/auth/realms/d4science/protocol/openid-connect/token", - "clientID": "conductor-ui", - "clientSecret": "b10d40b3-1f3c-47ce-baf4-d58bf6386eb3", - "callbackURL": "http://localhost/login/callback", - "logoutURL": "https://accounts.dev.d4science.org/auth/realms/d4science/protocol/openid-connect/logout", - "logoutCallbackURL": "http://localhost/logout/callback", - "roles": [ "admin", "viewer" ] - }, - "cookieSecret": "b10d40b3-1f3c-47ce-baf4-d58bf6386eb3", - "audit": true, - "acl": [ - "POST /(.*) admin", - "PUT /(.*) admin", - "DELETE /(.*) admin", - "GET /api/(.*) *", - "GET /(.*) viewer,admin" - ] -} - - diff --git a/local-site/pep.cfg b/local-site/pep.cfg deleted file mode 100644 index 7d287e6..0000000 --- a/local-site/pep.cfg +++ /dev/null @@ -1,28 +0,0 @@ -frontend http - - bind *:80 - - mode http - option http-keep-alive - - use_backend conductor-server_bck if { hdr_dom(host) -i conductor-server.local.net } - use_backend conductor-ui_bck if { hdr_dom(host) -i conductor-ui.local.net } - -# -# Backends -# - -backend conductor-server_bck - mode http - option httpchk - balance roundrobin - http-check send meth GET uri /api/health ver HTTP/1.1 hdr Host localhost - http-check expect rstatus (2|3)[0-9][0-9] - server-template conductor-server- 2 conductor-local_conductor-server:8080 check resolvers docker init-addr libc,none -backend conductor-ui_bck - mode http - option httpchk - balance roundrobin - http-check send meth GET uri / ver HTTP/1.1 hdr Host localhost - http-check expect rstatus (2|3)[0-9][0-9] - server-template conductor-ui- 2 conductor-local_conductor-ui:5000 check resolvers docker init-addr libc,none diff --git a/roles/cluster-replacement/defaults/main.yml b/roles/cluster-replacement/defaults/main.yml deleted file mode 100644 index ee1d13e..0000000 --- a/roles/cluster-replacement/defaults/main.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- -haproxy_latest_release: True -haproxy_version: 2.2 -haproxy_repo_key: 'http://haproxy.debian.net/bernat.debian.org.gpg' -haproxy_debian_latest_repo: "deb http://haproxy.debian.net {{ ansible_lsb.codename }}-backports-{{ haproxy_version }} main" -haproxy_ubuntu_latest_repo: "ppa:vbernat/haproxy-{{ haproxy_version }}" -haproxy_pkg_state: present -haproxy_enabled: True -haproxy_loglevel: info -haproxy_k_bind_non_local_ip: True -haproxy_docker_container: False -haproxy_docker_version: '{{ haproxy_version }}.4' -haproxy_docker_image: 'haproxytech/haproxy-debian:{{ haproxy_version }}.4' -haproxy_docker_compose_dir: /srv/haproxy_swarm -haproxy_docker_restart_policy: 'on-failure' - -haproxy_ha_with_keepalived: False -haproxy_docker_swarm_networks: - - '{{ docker_swarm_portainer_network }}' -haproxy_docker_swarm_additional_networks: [] - -haproxy_docker_swarm_haproxy_constraints: - - 'node.role == manager' -haproxy_docker_swarm_additional_services: [{ acl_name: 'conductor-server', acl_rule: 'hdr_dom(host) -i conductor-dev.int.d4science.net', stack_name: 'conductor-{{ infrastructure }}', service_name: 'conductor-server', service_replica_num: '2', service_port: '8080', service_overlay_network: 'conductor-network', stick_sessions: False, stick_on_cookie: True, stick_cookie: 'JSESSIONID', stick_table: 'type ip size 2m expire 180m', balance_type: 'roundrobin', backend_options: '', http_check_enabled: True, http_check: 'meth GET uri /api/health ver HTTP/1.1 hdr Host localhost', http_check_expect: 'rstatus (2|3)[0-9][0-9]' }, { acl_name: 'conductor-ui', acl_rule: 'hdr_dom(host) -i conductorui-dev.int.d4science.net', stack_name: 'conductor-{{ infrastructure }}', service_name: 'conductor-ui', service_replica_num: '2', service_port: '5000', service_overlay_network: 'conductor-network', stick_sessions: False, stick_on_cookie: True, stick_cookie: 'JSESSIONID', stick_table: 'type ip size 2m expire 180m', balance_type: 'roundrobin', backend_options: '', http_check_enabled: True, http_check: 'meth GET uri / ver HTTP/1.1 hdr Host localhost', http_check_expect: 'rstatus (2|3)[0-9][0-9]' }] -# - { acl_name: 'service', acl_rule: 'hdr_dom(host) -i service.example.com', stack_name: 'stack', service_name: 'service', service_replica_num: '1', service_port: '9999', service_overlay_network: 'service-network', stick_sessions: False, stick_on_cookie: True, stick_cookie: 'JSESSIONID', stick_table: 'type ip size 2m expire 180m', balance_type: 'roundrobin', backend_options: '', http_check_enabled: True, http_check: 'meth HEAD uri / ver HTTP/1.1 hdr Host localhost', http_check_expect: 'rstatus (2|3)[0-9][0-9]', allowed_networks: '192.168.1.0/24 192.168.2.0/24' } - -haproxy_default_port: 80 -haproxy_terminate_tls: False -haproxy_ssl_port: 443 -haproxy_admin_port: 8880 -haproxy_admin_socket: /run/haproxy/admin.sock - -haproxy_install_additional_pkgs: False -haproxy_additional_pkgs: - - haproxyctl - - haproxy-log-analysis - -haproxy_nagios_check: False -# It's a percentage -haproxy_nagios_check_w: 70 -haproxy_nagios_check_c: 90 - -# Used by some other role as defaults, eg docker-swarm -haproxy_spread_checks: 5 -haproxy_connect_timeout: 10s -haproxy_client_timeout: 120s -haproxy_server_timeout: 480s -haproxy_global_keepalive_timeout: 10s -haproxy_client_keepalive_timeout: 5184000s -haproxy_backend_maxconn: 2048 -haproxy_check_interval: 3s -haproxy_check_timeout: 2s -haproxy_maxconns: 4096 - -haproxy_sysctl_conntrack_max: 131072 - diff --git a/roles/cluster-replacement/tasks/main.yml b/roles/cluster-replacement/tasks/main.yml deleted file mode 100644 index 1ca5ff7..0000000 --- a/roles/cluster-replacement/tasks/main.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -- name: Generate haproxy config - template: - src: templates/haproxy.cfg.j2 - dest: "{{ target_path }}/haproxy.cfg" - -- name: Generate haproxy-docker-swarm - template: - src: templates/haproxy-docker-swarm.yaml.j2 - dest: "{{ target_path }}/haproxy-swarm.yaml" - -- name: Create the overlay network that will be joined by the proxied services - docker_network: - name: '{{ haproxy_docker_overlay_network }}' - driver: overlay - scope: swarm diff --git a/roles/cluster-replacement/templates/haproxy-docker-swarm.yaml.j2 b/roles/cluster-replacement/templates/haproxy-docker-swarm.yaml.j2 deleted file mode 100644 index 7424aba..0000000 --- a/roles/cluster-replacement/templates/haproxy-docker-swarm.yaml.j2 +++ /dev/null @@ -1,56 +0,0 @@ -version: '3.6' - -services: - haproxy: - image: {{ haproxy_docker_image }} - configs: - - source: haproxy-config - target: /usr/local/etc/haproxy/haproxy.cfg - networks: - - {{ haproxy_docker_overlay_network }} - volumes: - #- /etc/haproxy:/usr/local/etc/haproxy:ro - - /var/run/docker.sock:/var/run/docker.sock - ports: - - target: {{ haproxy_default_port }} - published: {{ haproxy_default_port }} - protocol: tcp - mode: host - - target: {{ haproxy_ssl_port }} - published: {{ haproxy_ssl_port }} - protocol: tcp - mode: host - - target: {{ haproxy_admin_port }} - published: {{ haproxy_admin_port }} - protocol: tcp - mode: host - dns: [127.0.0.11] - deploy: - mode: replicated - replicas: 1 - update_config: - parallelism: 1 - delay: 20s - placement: - constraints: - - "node.role==manager" - restart_policy: - condition: {{ haproxy_docker_restart_policy}} - delay: 20s - max_attempts: 5 - window: 120s - resources: - limits: - cpus: '2.0' - memory: 768M - reservations: - cpus: '1.0' - memory: 384M - logging: - driver: 'journald' -configs: - haproxy-config: - file: ./haproxy.cfg -networks: - {{ haproxy_docker_overlay_network }}: - external: true diff --git a/roles/cluster-replacement/templates/haproxy.cfg.j2 b/roles/cluster-replacement/templates/haproxy.cfg.j2 deleted file mode 100644 index 2d1932f..0000000 --- a/roles/cluster-replacement/templates/haproxy.cfg.j2 +++ /dev/null @@ -1,75 +0,0 @@ -global - log fd@2 local2 - chroot /var/lib/haproxy - pidfile /var/run/haproxy.pid - maxconn 4000 - user haproxy - group haproxy - stats socket /var/lib/haproxy/stats expose-fd listeners - master-worker - -resolvers docker - nameserver dns1 127.0.0.11:53 - resolve_retries 3 - timeout resolve 1s - timeout retry 1s - hold other 10s - hold refused 10s - hold nx 10s - hold timeout 10s - hold valid 10s - hold obsolete 10s - -defaults - timeout connect 10s - timeout client 30s - timeout server 30s - log global - monitor-uri /_haproxy_health_check - timeout http-keep-alive {{ haproxy_global_keepalive_timeout }} - timeout connect {{ haproxy_connect_timeout }} - timeout client {{ haproxy_client_timeout }} - timeout server {{ haproxy_server_timeout }} - timeout check {{ haproxy_check_timeout }} - timeout http-request 10s # slowloris protection - default-server inter 3s fall 2 rise 2 slowstart 60s - -# Needed to preserve the stick tables -peers mypeers - peer local_haproxy 127.0.0.1:1024 - -frontend http - - bind *:{{ haproxy_default_port }} - - mode http - option http-keep-alive - -{% for srv in haproxy_docker_swarm_additional_services %} - use_backend {{ srv.acl_name }}_bck if { {{ srv.acl_rule }} } -{% endfor %} - - -# -# Backends -# - -{% for srv in haproxy_docker_swarm_additional_services %} -backend {{ srv.acl_name }}_bck - mode http - option httpchk - balance {{ srv.balance_type | default('roundrobin') }} -{% if srv.http_check_enabled is defined and srv.http_check_enabled %} - http-check send {{ srv.http_check }} - http-check expect {{ srv.http_check_expect }} -{% endif %} -{% if srv.stick_sessions %} -{% if srv.stick_on_cookie %} - cookie {{ srv.stick_cookie }} -{% else %} - stick on src - stick-table {{ srv.stick_table }} -{% endif %} -{% endif %} - server-template {{ srv.service_name }}- {{ srv.service_replica_num }} {{ srv.stack_name }}_{{ srv.service_name }}:{{ srv.service_port }} {{ srv.backend_options | default('') }} check resolvers docker init-addr libc,none -{% endfor %} diff --git a/roles/cluster-replacement/vars/main.yml b/roles/cluster-replacement/vars/main.yml deleted file mode 100644 index f2502b6..0000000 --- a/roles/cluster-replacement/vars/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -haproxy_docker_overlay_network: 'haproxy-public' diff --git a/roles/common/defaults/main.yaml b/roles/common/defaults/main.yaml index e25f3ef..a7f01bc 100644 --- a/roles/common/defaults/main.yaml +++ b/roles/common/defaults/main.yaml @@ -1,4 +1,5 @@ --- +conductor_server: http://conductor-server:8080/api target_path: "/tmp/conductor_stack" conductor_network: conductor-network conductor_db: postgres diff --git a/roles/conductor/defaults/conductor_ui_secrets.yaml b/roles/conductor/defaults/conductor_ui_secrets.yaml new file mode 100644 index 0000000..2ebbb24 --- /dev/null +++ b/roles/conductor/defaults/conductor_ui_secrets.yaml @@ -0,0 +1,15 @@ +$ANSIBLE_VAULT;1.1;AES256 +64626566636365626537356334643266623431393062653538313362663664643538373137383732 +3865313539323962666165386336373633303066353634350a633231666363356238326130373561 +64336633343066323464343136336333613233396164623537623762323261383537633137363234 +3534616537666436370a326331316232613839656436646164343236356233646233623430623665 +64663265313964653063333133326636353162353532626364316433373030396434616434333631 +32333936666339303963636438616164343063393364666332323831363833323131653666303534 +62376162313737303036366532316163383434333130643363613166333433616331393636613635 +30613132636261613165613136356638353532663634393431383739363636323961323538383566 +62316261373262663335393632376366383031306563343531643632633234346531633164303038 +62633039363961613538393832623039383237623663366430313238653030376263613032663437 +38386635303332386630386133366232343966393761643635313833316536386634633563326639 +38626435393963643866363663353834343333346139363565353161663737393166613938353562 +62623661326237353163623138386432376531353864383036613931643164333633646431353162 +3666373032663262623438353236626436303132306436326636 diff --git a/roles/conductor/defaults/main.yaml b/roles/conductor/defaults/main.yaml index 00a8a60..1d94394 100644 --- a/roles/conductor/defaults/main.yaml +++ b/roles/conductor/defaults/main.yaml @@ -1,5 +1,12 @@ --- -conductor_replicas: 2 +conductor_replicas: 1 +conductor_ui_replicas: 1 +conductor_image: nubisware/conductor-server:3.0.4 +conductor_ui_image: nubisware/conductor-ui-oauth2:3.0.4 conductor_config: conductor-swarm-config.properties conductor_config_template: "{{ conductor_config }}.j2" +#nw_cluster_conductor_ui_secret: in vault +#dev_conductor_ui_secret: in vault +#pre_conductor_ui_secret: in vault +#prod_conductor_ui_secret: in vault diff --git a/roles/conductor/tasks/main.yaml b/roles/conductor/tasks/main.yaml index 3e42e03..bd736dd 100644 --- a/roles/conductor/tasks/main.yaml +++ b/roles/conductor/tasks/main.yaml @@ -4,28 +4,14 @@ src: templates/conductor-swarm.yaml.j2 dest: "{{ target_path }}/conductor-swarm.yaml" -- name: Generate auth config +- name: Generate local auth config + when: conductor_auth is defined template: - src: templates/auth.cfg.j2 + src: "templates/{{ conductor_auth }}_auth.cfg.j2" dest: "{{ target_path }}/auth.cfg" -- name: Generate conductor config from dynomite seeds - when: conductor_db is defined and conductor_db == 'dynomite' - vars: - seeds: "{{ lookup('file', '{{ target_path}}/seeds.list').splitlines() }}" - template: - src: "templates/{{ conductor_config_template }}" - dest: "{{ target_path }}/{{ conductor_config }}" - - name: Generate conductor config for JDBC DB when: conductor_db is not defined or conductor_db != 'dynomite' template: src: "templates/{{ conductor_config_template }}" dest: "{{ target_path }}/{{ conductor_config }}" - -- name: Copy conductor SQL schema init for JDBC DB - when: (conductor_db is not defined or conductor_db != 'dynomite') and init_db - template: - src: "templates/conductor-db-init-{{ conductor_db }}.sql.j2" - dest: "{{ target_path }}/conductor-db-init.sql" - diff --git a/roles/conductor/templates/auth.cfg.j2 b/roles/conductor/templates/auth.cfg.j2 deleted file mode 100644 index 92470b9..0000000 --- a/roles/conductor/templates/auth.cfg.j2 +++ /dev/null @@ -1,23 +0,0 @@ -{ - "strategy": "local", - "strategySettings":{ - "users": { - "admin": { - "hash": "098039dd5e84e486f83eadefc31ce038ccc90d6d62323528181049371c9460b4", - "salt": "salt", - "displayName": "Admin", - "email": "marco.lettere@nubisware.com", - "roles": [ "admin", "viewer" ] - } - } - }, - "audit": true, - "acl": [ - "POST /(.*) admin", - "PUT /(.*) admin", - "DELETE /(.*) admin", - "GET /api/(.*) viewer", - "GET /(.*) *" - ] -} - diff --git a/roles/conductor/templates/conductor-swarm-config.properties.j2 b/roles/conductor/templates/conductor-swarm-config.properties.j2 index 4c3cff0..280ed85 100644 --- a/roles/conductor/templates/conductor-swarm-config.properties.j2 +++ b/roles/conductor/templates/conductor-swarm-config.properties.j2 @@ -1,92 +1,31 @@ # Servers. -conductor.jetty.server.enabled=true -conductor.grpc.server.enabled=false +conductor.grpc-server.enabled=false -# Database persistence model. Possible values are memory, redis, and dynomite. -# If ommitted, the persistence used is memory -# -# memory : The data is stored in memory and lost when the server dies. Useful for testing or demo -# redis : non-Dynomite based redis instance -# dynomite : Dynomite cluster. Use this for HA configuration. +# Database persistence type. {% if conductor_db is not defined or conductor_db == 'postgres' %} -db=postgres -jdbc.url={{ postgres_jdbc_url }} -jdbc.username={{ postgres_jdbc_user }} -jdbc.password={{ postgres_jdbc_pass }} -conductor.{{ conductor_db }}.connection.pool.size.max=10 -conductor.{{ conductor_db }}.connection.pool.idle.min=2 -flyway.enabled=false - -{% elif conductor_db is defined and conductor_db == 'mysql' %} -db=mysql -jdbc.url={{ mysql_jdbc_url }} -jdbc.username={{ mysql_jdbc_user }} -jdbc.password={{ mysql_jdbc_pass }} -conductor.{{ conductor_db }}.connection.pool.size.max=10 -conductor.{{ conductor_db }}.connection.pool.idle.min=2 -flyway.enabled=false - - -{% else %} -db=dynomite - -# Dynomite Cluster details. -# format is host:port:rack separated by semicolon -workflow.dynomite.cluster.hosts={% set ns = namespace() %} -{% set ns.availability_zone = "" %} -{% for seed in seeds %} -{% set ns.seed_tokens = seed.split(':') %} -{% if ns.availability_zone == "" %} -{% set ns.availability_zone = ns.seed_tokens[2] %} -{% endif %} -{% if ns.availability_zone == ns.seed_tokens[2] %} -{{ ns.seed_tokens[0] }}:8102:{{ ns.availability_zone }}{%- if not loop.last %};{%- endif %} -{% endif %} -{%- endfor %} - - -# If you are running using dynomite, also add the following line to the property -# to set the rack/availability zone of the conductor server to be same as dynomite cluster config -EC2_AVAILABILTY_ZONE={{ ns.availability_zone }} - -# Dynomite cluster name -workflow.dynomite.cluster.name=dyno1 - -# Namespace for the keys stored in Dynomite/Redis -workflow.namespace.prefix=conductor - -# Namespace prefix for the dyno queues -workflow.namespace.queue.prefix=conductor_queues - -# No. of threads allocated to dyno-queues (optional) -queues.dynomite.threads=3 +conductor.db.type=postgres +conductor.postgres.jdbcUrl={{ postgres_jdbc_url }} +conductor.postgres.jdbcUsername={{ postgres_jdbc_user }} +conductor.postgres.jdbcPassword={{ postgres_jdbc_pass }} +{% endif %} -# Non-quorum port used to connect to local redis. Used by dyno-queues. -# When using redis directly, set this to the same port as redis server -# For Dynomite, this is 22122 by default or the local redis-server port used by Dynomite. -queues.dynomite.nonQuorum.port=22122 +{% if conductor_db == 'imysql' %} +conductor.db.type=mysql +conductor.mysql.jdbcUrl={{ mysql_jdbc_url }} +conductor.mysql.jdbcUsername={{ mysql_jdbc_user }} +conductor.mysql.jdbcPassword={{ mysql_jdbc_pass }} {% endif %} -# Elastic search instance type. Possible values are memory and external. -# If not specified, the instance type will be embedded in memory -# -# memory: The instance is created in memory and lost when the server dies. Useful for development and testing. -# external: Elastic search instance runs outside of the server. Data is persisted and does not get lost when -# the server dies. Useful for more stable environments like staging or production. -workflow.elasticsearch.instanceType=external +# Hikari pool sizes are -1 by default and prevent startup +conductor.{{conductor_db}}.connectionPoolMaxSize=10 +conductor.{{conductor_db}}.connectionPoolMinIdle=2 -# Transport address to elasticsearch -workflow.elasticsearch.url=elasticsearch:9300 -# Name of the elasticsearch cluster +# Elastic search instance indexing is enabled. +conductor.indexing.enabled=true +conductor.elasticsearch.url=http://elasticsearch:9200 +workflow.elasticsearch.instanceType=EXTERNAL workflow.elasticsearch.index.name=conductor -# Additional modules (optional) -# conductor.additional.modules=class_extending_com.google.inject.AbstractModule - -# Additional modules for metrics collection (optional) -# conductor.additional.modules=com.netflix.conductor.contribs.metrics.MetricsRegistryModule,com.netflix.conductor.contribs.metrics.LoggingMetricsModule -# com.netflix.conductor.contribs.metrics.LoggingMetricsModule.reportPeriodSeconds=15 - # Load sample kitchen sink workflow loadSample=false diff --git a/roles/conductor/templates/conductor-swarm.yaml.j2 b/roles/conductor/templates/conductor-swarm.yaml.j2 index ef465ab..3dda063 100644 --- a/roles/conductor/templates/conductor-swarm.yaml.j2 +++ b/roles/conductor/templates/conductor-swarm.yaml.j2 @@ -6,28 +6,19 @@ services: conductor-server: environment: - CONFIG_PROP={{ conductor_config }} - image: nubisware/conductor-server + image: "{{ conductor_image }}" networks: - {{ conductor_network }} -{% if clustered %} - - {{ haproxy_docker_overlay_network }} -{% endif %} -{% if not clustered %} - ports: - - "8080:8080" -{% endif %} deploy: mode: replicated replicas: {{ conductor_replicas }} -{% if clustered %} - endpoint_mode: dnsrr -{% endif %} +{% if infrastructure != 'local' %} placement: constraints: [node.role == worker] +{% endif %} restart_policy: condition: on-failure delay: 5s - max_attempts: 3 window: 120s configs: - source: swarm-config @@ -40,42 +31,33 @@ services: environment: - WF_SERVER=http://conductor-server:8080/api/ - AUTH_CONFIG_PATH=/app/config/auth.config - image: nubisware/conductor-ui + image: "{{ conductor_ui_image }}" networks: - {{ conductor_network }} -{% if clustered %} - - {{ haproxy_docker_overlay_network }} -{% endif %} -{% if not clustered %} - ports: - - "5000:5000" -{% endif %} +{% if conductor_auth is defined %} configs: - source: auth-config target: /app/config/auth.config +{% endif %} deploy: mode: replicated - replicas: {{ conductor_replicas }} -{% if clustered %} - endpoint_mode: dnsrr -{% endif %} + replicas: {{ conductor_ui_replicas }} +{% if infrastructure != 'local' %} placement: constraints: [node.role == worker] +{% endif %} restart_policy: condition: on-failure delay: 5s - max_attempts: 3 window: 120s networks: {{ conductor_network }}: -{% if clustered %} - {{ haproxy_docker_overlay_network }}: - external: True -{% endif %} configs: swarm-config: file: ./{{ conductor_config }} +{% if conductor_auth is defined %} auth-config: file: ./auth.cfg +{% endif %} diff --git a/local-site/auth.cfg b/roles/conductor/templates/local_auth.cfg.j2 similarity index 100% rename from local-site/auth.cfg rename to roles/conductor/templates/local_auth.cfg.j2 diff --git a/roles/conductor/templates/oauth2_auth.cfg.j2 b/roles/conductor/templates/oauth2_auth.cfg.j2 new file mode 100644 index 0000000..27005ff --- /dev/null +++ b/roles/conductor/templates/oauth2_auth.cfg.j2 @@ -0,0 +1,24 @@ +{ + "strategy": "oauth2", + "strategySettings": { + "authorizationURL": "{{ iam_host }}/auth/realms/d4science/protocol/openid-connect/auth", + "tokenURL": "{{ iam_host }}/auth/realms/d4science/protocol/openid-connect/token", + "clientID": "conductor-ui", + "clientSecret": "{{ conductor_ui_secret }}", + "callbackURL": "http://conductor-ui/login/callback", + "logoutURL": "{{ iam_host }}/auth/realms/d4science/protocol/openid-connect/logout", + "logoutCallbackURL": "http://conductor-ui/logout/callback", + "roles": [ "admin", "viewer" ] + }, + "cookieSecret": "{{ conductor_ui_secret }}", + "audit": true, + "acl": [ + "POST /(.*) admin", + "PUT /(.*) admin", + "DELETE /(.*) admin", + "GET /api/(.*) *", + "GET /(.*) viewer,admin" + ] +} + + diff --git a/roles/databases/templates/elasticsearch-swarm.yaml.j2 b/roles/databases/templates/elasticsearch-swarm.yaml.j2 index 9b00953..092202e 100644 --- a/roles/databases/templates/elasticsearch-swarm.yaml.j2 +++ b/roles/databases/templates/elasticsearch-swarm.yaml.j2 @@ -3,7 +3,7 @@ version: '3.6' services: elasticsearch: - image: docker.elastic.co/elasticsearch/elasticsearch:5.6.8 + image: docker.elastic.co/elasticsearch/elasticsearch:6.8.15 environment: - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - transport.host=0.0.0.0 @@ -18,9 +18,10 @@ services: deploy: mode: replicated replicas: {{ elasticsearch_replicas }} - #endpoint_mode: dnsrr +{% if infrastructure != 'local' %} placement: constraints: [node.role == worker] +{% endif %} restart_policy: condition: on-failure delay: 5s diff --git a/roles/databases/templates/mysql-swarm.yaml.j2 b/roles/databases/templates/mysql-swarm.yaml.j2 index 37f1dd7..59977a7 100644 --- a/roles/databases/templates/mysql-swarm.yaml.j2 +++ b/roles/databases/templates/mysql-swarm.yaml.j2 @@ -9,22 +9,14 @@ services: MYSQL_PASSWORD: {{ mysql_jdbc_pass }} MYSQL_ROOT_PASSWORD: {{ mysql_jdbc_pass }} MYSQL_DB: {{ mysql_jdbc_db }} -{% if init_db %} - configs: - - source: db-init - target: "/docker-entrypoint-initdb.d/db-init.sql" -{% endif %} networks: - {{ conductor_network }} deploy: replicas: {{ mysql_replicas }} +{% if infrastructure == 'local' %} placement: constraints: [node.role == worker] +{% endif %} networks: {{ conductor_network }}: -{% if init_db %} -configs: - db-init: - file: {{ target_path }}/conductor-db-init.sql -{% endif %} diff --git a/roles/databases/templates/postgres-swarm.yaml.j2 b/roles/databases/templates/postgres-swarm.yaml.j2 index 56d3f56..bee5c6c 100644 --- a/roles/databases/templates/postgres-swarm.yaml.j2 +++ b/roles/databases/templates/postgres-swarm.yaml.j2 @@ -4,28 +4,17 @@ services: {{ postgres_service_name }}: image: postgres - ports: - - "5432:5432" environment: POSTGRES_USER: "{{ postgres_jdbc_user }}" POSTGRES_PASSWORD: "{{ postgres_jdbc_pass }}" POSTGRES_DB: "{{ postgres_jdbc_db }}" -{% if init_db %} - configs: - - source: db-init - target: "/docker-entrypoint-initdb.d/db-init.sql" -{% endif %} networks: - {{ conductor_network }} deploy: replicas: {{ postgres_replicas }} +{% if infrastructure != 'local' %} placement: constraints: [node.role == worker] - +{% endif %} networks: {{ conductor_network }}: -{% if init_db %} -configs: - db-init: - file: {{ target_path }}/conductor-db-init.sql -{% endif %} diff --git a/roles/elasticsearch/templates/elasticsearch-swarm.yaml.j2 b/roles/elasticsearch/templates/elasticsearch-swarm.yaml.j2 index 9b00953..067118a 100644 --- a/roles/elasticsearch/templates/elasticsearch-swarm.yaml.j2 +++ b/roles/elasticsearch/templates/elasticsearch-swarm.yaml.j2 @@ -3,7 +3,7 @@ version: '3.6' services: elasticsearch: - image: docker.elastic.co/elasticsearch/elasticsearch:5.6.8 + image: docker.elastic.co/elasticsearch/elasticsearch:6.8.15 environment: - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - transport.host=0.0.0.0 @@ -18,13 +18,13 @@ services: deploy: mode: replicated replicas: {{ elasticsearch_replicas }} - #endpoint_mode: dnsrr +{% if infrastructure !== 'local' %} placement: constraints: [node.role == worker] +{% endif %} restart_policy: condition: on-failure delay: 5s - max_attempts: 3 window: 120s networks: diff --git a/roles/mysql/defaults/main.yml b/roles/mysql/defaults/main.yml deleted file mode 100644 index 5bde0a2..0000000 --- a/roles/mysql/defaults/main.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -use_jdbc: True -mysql_image_name: 'mariadb' -mysql_service_name: 'mysqldb' -mysql_replicas: 1 -conductor_db: mysql -jdbc_user: conductor -jdbc_pass: password -jdbc_db: conductor -jdbc_url: jdbc:mysql://{{ mysql_service_name }}:3306/{{ mysql_jdbc_db }}?useSSL=false&allowPublicKeyRetrieval=true diff --git a/roles/mysql/tasks/main.yaml b/roles/mysql/tasks/main.yaml deleted file mode 100644 index 780b145..0000000 --- a/roles/mysql/tasks/main.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -- name: "Generate mysql swarm, image used: {{ mysql_image_name }}" - template: - src: templates/mysql-swarm.yaml.j2 - dest: "{{ target_path }}/mysql-swarm.yaml" diff --git a/roles/mysql/templates/mysql-swarm.yaml.j2 b/roles/mysql/templates/mysql-swarm.yaml.j2 deleted file mode 100644 index eb841cb..0000000 --- a/roles/mysql/templates/mysql-swarm.yaml.j2 +++ /dev/null @@ -1,30 +0,0 @@ -version: '3.6' - -services: - - {{ mysql_service_name }}: - image: {{ mysql_image_name }} - environment: - MYSQL_USER: {{ mysql_jdbc_user }} - MYSQL_PASSWORD: {{ mysql_jdbc_pass }} - MYSQL_ROOT_PASSWORD: {{ mysql_jdbc_pass }} - MYSQL_DB: {{ jdbc_db }} -{% if init_db %} - configs: - - source: db-init - target: "/docker-entrypoint-initdb.d/db-init.sql" -{% endif %} - networks: - - {{ conductor_network }} - deploy: - replicas: {{ mysql_replicas }} - placement: - constraints: [node.role == worker] - -networks: - {{ conductor_network }}: -{% if init_db %} -configs: - db-init: - file: {{ target_path }}/conductor-db-init.sql -{% endif %} diff --git a/roles/pep/defaults/main.yaml b/roles/pep/defaults/main.yaml new file mode 100644 index 0000000..bc026e9 --- /dev/null +++ b/roles/pep/defaults/main.yaml @@ -0,0 +1,2 @@ +pep_port: 80 +#pep_credentials: in vault diff --git a/roles/pep/defaults/pep_credentials.yaml b/roles/pep/defaults/pep_credentials.yaml new file mode 100644 index 0000000..130fa06 --- /dev/null +++ b/roles/pep/defaults/pep_credentials.yaml @@ -0,0 +1,18 @@ +$ANSIBLE_VAULT;1.1;AES256 +64326266376663626435303764383036326164336561303030633464333131373161336461326162 +3630623962383434623834313737616435613966343637390a353562636535376539353538353061 +31383933313734646661633661353836386266393565633830353137646431613431663236376137 +6362613731386433370a323934373363383565323337373239666434353036333435613061666231 +34346134313038366165343861316233326331393732353334303039616535633866366261623764 +64653630353830396665363862633730396432633062363932636335643136613237373339613139 +32386330396237396363383638653431663864333162303936663563313535343536376139343166 +65316137326533306335643833353338376533633733393333623131316662386334653633353332 +66363734636237363637303863323638393339373364356433666466643038343930616166396136 +61666232356337613431316662353766393335306232616266363933653032656536386562373665 +36306234636233313237623364613033313261393431633139343037623732646431663139383062 +30396230326432376335303362356534613937306431636361663335376265363139366463656638 +31386430393037306233663161333465616236383134623961343732383633386665333231363036 +64346630633337643961653464613336623363303737626231326138633736656530653138326537 +35386161656461313034343935353863333635376664386565393530633532613965646662363634 +65396137646561353534373536616162353631383130363466356637643639323333643964323638 +3535 diff --git a/roles/pep/tasks/main.yaml b/roles/pep/tasks/main.yaml new file mode 100644 index 0000000..fe134f0 --- /dev/null +++ b/roles/pep/tasks/main.yaml @@ -0,0 +1,34 @@ +--- +- name: Generate PEP config + template: + src: templates/nginx.conf.j2 + dest: "{{ target_path }}/nginx.conf" + +- name: Generate PEP default config + when: pep is defined and pep == True + template: + src: templates/nginx.default.conf.j2 + dest: "{{ target_path }}/nginx.default.conf" + +- name: Generate PEP default config + when: pep is not defined or pep == False + template: + src: templates/nginx.default.conf.nopep.j2 + dest: "{{ target_path }}/nginx.default.conf" + +- name: Generate config.js + when: pep is defined and pep == True + template: + src: templates/config.js.j2 + dest: "{{ target_path }}/config.js" + +- name: Generate pep.js + when: pep is defined and pep == True + template: + src: templates/pep.js.j2 + dest: "{{ target_path }}/pep.js" + +- name: Generate pep-docker-swarm + template: + src: templates/pep-swarm.yaml.j2 + dest: "{{ target_path }}/pep-swarm.yaml" diff --git a/roles/pep/templates/config.js.j2 b/roles/pep/templates/config.js.j2 new file mode 100644 index 0000000..ea76293 --- /dev/null +++ b/roles/pep/templates/config.js.j2 @@ -0,0 +1,98 @@ +export default { config }; + +var config = { + "pep-credentials" : "{{ pep_credentials }}", + "hosts" : [ + { + "host": "conductor-server", + "allow-basic-auth" : true, + "pip" : [ { claim: "context", operator : "get-contexts" } ], + "paths" : [ + { + "name" : "metadata", + "path" : "^/api/metadata/(taskdefs|workflow)/?.*$", + "methods" : [ + { + "method" : "GET", + "scopes" : ["get","list"] + } + ] + }, + { + "name" : "metadata.taskdefs", + "path" : "^/api/metadata/taskdefs/?.*$", + "methods" : [ + { + "method" : "POST", + "scopes" : ["create"] + }, + { + "method" : "DELETE", + "scopes" : ["delete"], + }, + { + "method" : "PUT", + "scopes" : ["update"], + } + ] + }, + { + "name" : "metadata.workflow", + "path" : "^/api/metadata/workflow/?.*$", + "methods" : [ + { + "method" : "POST", + "scopes" : ["create"] + }, + { + "method" : "DELETE", + "scopes" : ["delete"], + }, + { + "method" : "PUT", + "scopes" : ["update"], + } + ] + }, + { + "name" : "workflow", + "path" : "^/api/workflow/?.*$", + "methods" : [ + { + "method" : "GET", + "scopes" : ["get"], + }, + { + "method" : "POST", + "scopes" : ["start"], + }, + { + "method" : "DELETE", + "scopes" : ["terminate"], + } + ] + }, + { + "name" : "task", + "path" : "^/api/tasks/poll/.+$", + "methods" : [ + { + "method" : "GET", + "scopes" : ["poll"], + } + ] + }, + { + "name" : "task", + "path" : "^/api/tasks$", + "methods" : [ + { + "method" : "POST", + "scopes" : ["update"], + } + ] + } + ] + } + ] +} diff --git a/roles/pep/templates/nginx.conf.j2 b/roles/pep/templates/nginx.conf.j2 new file mode 100644 index 0000000..4a326d9 --- /dev/null +++ b/roles/pep/templates/nginx.conf.j2 @@ -0,0 +1,18 @@ +load_module modules/ngx_http_js_module.so; + +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + +{% if pep is defined and pep == True %} + js_import pep.js; + js_set $authorization pep.enforce; + proxy_cache_path /var/cache/nginx/pep keys_zone=token_responses:1m max_size=2m; +{% endif %} + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} diff --git a/roles/pep/templates/nginx.default.conf.j2 b/roles/pep/templates/nginx.default.conf.j2 new file mode 100644 index 0000000..84c7549 --- /dev/null +++ b/roles/pep/templates/nginx.default.conf.j2 @@ -0,0 +1,96 @@ +upstream _conductor-server { + ip_hash; + server conductor-server:8080; +} + +upstream _conductor-ui { + ip_hash; + server conductor-ui:5000; +} + +map $http_authorization $source_auth { + default ""; +} + +js_var $auth_token; +js_var $pep_credentials; + +server { + + listen *:80; + listen [::]:80; + server_name conductor-server; + + location / { + proxy_pass http://_conductor-server; + } + + location /api/ { + js_content pep.enforce; + } + + location @backend { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Original-URI $request_uri; + proxy_pass http://_conductor-server; + } + + location /jwt_verify_request { + internal; + proxy_method POST; + proxy_http_version 1.1; + proxy_set_header Authorization $pep_credentials; + proxy_set_header Content-Type "application/x-www-form-urlencoded"; + proxy_pass "{{ iam_host }}/auth/realms/d4science/protocol/openid-connect/token/introspect"; + + proxy_cache token_responses; # Enable caching + proxy_cache_key $source_auth; # Cache for each source authentication + proxy_cache_lock on; # Duplicate tokens must wait + proxy_cache_valid 200 10s; # How long to use each response + proxy_ignore_headers Cache-Control Expires Set-Cookie; + } + + location /jwt_request { + internal; + proxy_method POST; + proxy_http_version 1.1; + proxy_set_header Authorization $pep_credentials; + proxy_set_header Content-Type "application/x-www-form-urlencoded"; + proxy_pass "{{ iam_host }}/auth/realms/d4science/protocol/openid-connect/token"; + } + + location /permission_request { + internal; + proxy_method POST; + proxy_http_version 1.1; + proxy_set_header Content-Type "application/x-www-form-urlencoded"; + proxy_set_header Authorization "Bearer $auth_token"; + proxy_pass "{{ iam_host }}/auth/realms/d4science/protocol/openid-connect/token"; + } + +} + +server { + + listen *:80 default_server; + listen [::]:80 default_server; + server_name conductor-ui; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://_conductor-ui; + } + +} diff --git a/roles/pep/templates/nginx.default.conf.nopep.j2 b/roles/pep/templates/nginx.default.conf.nopep.j2 new file mode 100644 index 0000000..59396a4 --- /dev/null +++ b/roles/pep/templates/nginx.default.conf.nopep.j2 @@ -0,0 +1,40 @@ +upstream _conductor-server { + ip_hash; + server conductor-server:8080; +} + +upstream _conductor-ui { + ip_hash; + server conductor-ui:5000; +} + +server { + + listen *:80; + listen [::]:80; + server_name conductor-server; + + location / { + proxy_pass http://_conductor-server; + } + +} + +server { + + listen *:80 default_server; + listen [::]:80 default_server; + server_name conductor-ui; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://_conductor-ui; + } + +} diff --git a/roles/pep/templates/pep-swarm.yaml.j2 b/roles/pep/templates/pep-swarm.yaml.j2 new file mode 100644 index 0000000..0dddaae --- /dev/null +++ b/roles/pep/templates/pep-swarm.yaml.j2 @@ -0,0 +1,35 @@ +version: '3.6' + +services: + + pep: + image: nginx:stable-alpine + networks: + - conductor-network + ports: + - "{{ pep_port }}:80" +{% if pep is defined and pep == True %} + volumes: + - "./pep.js:/etc/nginx/pep.js" + - "./config.js:/etc/nginx/config.js" +{% endif %} + deploy: + replicas: 1 + restart_policy: + condition: on-failure + delay: 10s + window: 120s + configs: + - source: nginxconf + target: /etc/nginx/templates/default.conf.template + - source: nginxbaseconf + target: /etc/nginx/nginx.conf + +networks: + conductor-network: + +configs: + nginxconf: + file: ./nginx.default.conf + nginxbaseconf: + file: ./nginx.conf diff --git a/roles/pep/templates/pep.js.j2 b/roles/pep/templates/pep.js.j2 new file mode 100644 index 0000000..aa42f2b --- /dev/null +++ b/roles/pep/templates/pep.js.j2 @@ -0,0 +1,299 @@ +export default { enforce }; + +import defaultExport from './config.js'; + +function log(c, s){ + c.request.error(s) +} + +function enforce(r) { + + var context = { + request: r , + config : defaultExport["config"], + backend : (defaultExport.backend ? defaultExport.backend : "@backend"), + export_backend_headers : (defaultExport.backendHeaders ? defaultExport.backendHeaders : wkf.export_backend_headers) + } + + log(context, "Inside NJS enforce for " + r.method + " @ " + r.headersIn.host + "/" + r.uri) + + context = computeProtection(context) + + wkf.run(wkf.build(context), context) +} + +// ######## WORKFLOW FUNCTIONS ############### +var wkf = { + + build : (context)=>{ + var actions = [ + "export_pep_credentials", + "parse_authentication", + "check_authentication", + "export_authn_token", + "pip", + "pdp", + "export_backend_headers", + "pass" + ] + return actions + }, + + run : (actions, context) => { + context.request.error("Starting workflow with " + njs.dump(actions)) + var w = actions.reduce( + (acc, f) => acc.then(typeof(f) === "function" ? f : wkf[f]), + Promise.resolve().then(()=>context) + ) + w.catch(e => { context.request.error(njs.dump(e)); context.request.return(401)} ) + }, + + export_pep_credentials : exportPepCredentials, + export_authn_token : exportAuthToken, + export_backend_headers : c=>c, + parse_authentication : parseAuthentication, + check_authentication : checkAuthentication, + verify_token : verifyToken, + request_token : requestToken, + pip : pipExecutor, + pdp : pdpExecutor, + pass : pass, + + //PIP utilities + "get-path-component" : (c, i) => c.request.uri.split("/")[i], + "get-token-field" : getTokenField, + "get-contexts" : (c) => { + var ra = c.authn.verified_token["resource_access"] + if(ra){ + var out = []; + for(var k in ra){ + if(ra[k].roles && ra[k].roles.length !== 0) out.push(k) + } + } + return out; + } +} + +function getTokenField(context, f){ + return context.authn.verified_token[f] +} + +function exportVariable(context, name, value){ + context.request.variables[name] = value + log(context, "Exported variables:" + njs.dump(context.request.variables)) + return context +} + +function exportPepCredentials(context){ + if(!context.config["pep-credentials"]){ + throw new Error("Need PEP credentials") + } + return exportVariable(context, "pep_credentials", "Basic " + context.config["pep-credentials"]) +} + +function exportAuthToken(context){ + return exportVariable(context, "auth_token", context.authn.token) +} + +function checkAuthentication(context){ + return context.authn.type === "bearer" ? wkf.verify_token(context) : wkf.request_token(context) +} + +function parseAuthentication(context){ + context.request.log("Inside parseAuthentication") + var incomingauth = context.request.headersIn["Authorization"] + + if(!incomingauth) throw new Error("Authentication required"); + + var arr = incomingauth.trim().replace(/\s\s+/g, " ").split(" ") + if(arr.length != 2) throw new Error("Unknown authentication scheme"); + + var type = arr[0].toLowerCase() + if(type === "basic" && context.authz.host && context.authz.host["allow-basic-auth"]){ + var unamepass = Buffer.from(arr[1], 'base64').toString().split(":") + if(unamepass.length != 2) return null; + context.authn = { type : type, raw : arr[1], user : unamepass[0], password : unamepass[1]} + return context + }else if(type === "bearer"){ + context.authn = { type : type, raw : arr[1], token : arr[1]} + return context + } + throw new Error("Unknown authentication scheme"); +} + +function verifyToken(context){ + log(context, "Inside verifyToken") + var options = { + "body" : "token=" + context.authn.token + "&token_type_hint=access_token" + } + return context.request.subrequest("/jwt_verify_request", options) + .then(reply=>{ + if (reply.status === 200) { + var response = JSON.parse(reply.responseBody); + if (response.active === true) { + return response + } else { + throw new Error("Unauthorized") + } + } else { + throw new Error("Unauthorized") + } + }).then(verified_token => { + context.authn.verified_token = + JSON.parse(Buffer.from(context.authn.token.split('.')[1], 'base64url').toString()) + return context + }) +} + +function requestToken(context){ + log(context, "Inside requestToken") + var options = { + "body" : "grant_type=password&username="+context.authn.user+"&password="+context.authn.password + } + return context.request.subrequest("/jwt_request", options) + .then(reply=>{ + if (reply.status === 200) { + var response = JSON.parse(reply.responseBody); + context.authn.token = response.access_token + context.authn.verified_token = + JSON.parse(Buffer.from(context.authn.token.split('.')[1], 'base64url').toString()) + return context + } else { + throw new Error("Unauthorized") + } + }) +} + +function pipExecutor(context){ + log(context, "Inside extra claims PIP") + context.authz.pip.forEach(extra =>{ + //call extra claim pip function + try{ + var operator = extra.operator + var result = wkf[operator](context, extra.args) + //ensure array and add to extra_claims + if(!(result instanceof Array)) result = [result] + if(!context.extra_claims) context.extra_claims = {}; + context.extra_claims[extra.claim] = result + } catch (error){ + log(context, "Skipping invalid extra claim " + njs.dump(error)) + } + }) + log(context, "Extra claims are " + njs.dump(context.extra_claims)) + return context +} + +function pdpExecutor(context){ + log(context, "Inside PDP") + return context.authz.pdp(context) +} + +function umaCall(context){ + log(context, "Inside UMA call") + var options = { "body" : computePermissionRequestBody(context) }; + return context.request.subrequest("/permission_request", options) + .then(reply =>{ + if(reply.status === 200){ + return context + }else{ + throw new Error("Response for authorization request is not ok " + reply.status + " " + njs.dump(reply.responseBody)) + } + }) +} + +function pass(context){ + log(context, "Inside pass"); + if(typeof(context.backend) === "string") context.request.internalRedirect(context.backend); + else if (typeof(context.backend) === "function") context.request.internalRedirect(context.backend(context)) + return context; +} + +// ######## AUTHORIZATION PART ############### +function computePermissionRequestBody(context){ + + if(!context.authz.host || !context.authz.path ){ + throw new Error("Enforcemnt mode is always enforcing. Host or path not found...") + } + + var audience = computeAudience(context) + var grant = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" + var mode = "response_mode=decision" + var permissions = computePermissions(context) + var extra = "" + if(context.extra_claims){ + extra = + "claim_token_format=urn:ietf:params:oauth:token-type:jwt&claim_token=" + + JSON.stringify(context.extra_claims).toString("base64url") + } + var body = audience + "&" + grant + "&" + permissions + "&" + mode + "&" + extra + context.request.error("Computed permission request body is " + body) + return body +} + +function computeAudience(context){ + var aud = context.request.headersIn.host + if(context.authz.host){ + aud = context.authz.host.audience||context.authz.host.host + } + return "audience=" + aud +} + +function computePermissions(context){ + var resource = context.request.uri + if(context.authz.path){ + resource = context.authz.path.name||context.authz.path.path + } + var scopes = [] + if(context.authz.method && context.authz.method.scopes){ + scopes = context.authz.method.scopes + } + if(scopes.length > 0){ + return scopes.map(s=>"permission=" + resource + "#" + s).join("&") + } + return "permission=" + resource +} + +function getPath(hostconfig, incomingpath, incomingmethod){ + var paths = hostconfig.paths || [] + var matchingpaths = paths + .filter(p => {return incomingpath.match(p.path) != null}) + .reduce((acc, p) => { + if (!p.methods || p.methods.length === 0) acc.weak.push({ path: p}); + else{ + var matchingmethods = p.methods.filter(m=>m.method.toUpperCase() === incomingmethod) + if(matchingmethods.length > 0) acc.strong.push({ method : matchingmethods[0], path: p}); + } + return acc; + }, { strong: [], weak: []}) + return matchingpaths.strong.concat(matchingpaths.weak)[0] +} + +function getHost(config, host){ + var matching = config.hosts.filter(h=>{ + return h.host === host + }) + return matching.length > 0 ? matching[0] : null +} + +function computeProtection(context){ + log(context, "Getting by host " + context.request.headersIn.host) + context.authz = {} + context.authz.host = getHost(context.config, context.request.headersIn.host) + if(context.authz.host !== null){ + context.authz.pip = context.authz.host.pip ? context.authz.host.pip : []; + context.authz.pdp = context.authz.host.pdp ? context.authz.host.pdp : umaCall; + var pathandmethod = getPath(context.authz.host, context.request.uri, context.request.method); + if(pathandmethod){ + context.authz.path = pathandmethod.path; + context.authz.pip = context.authz.path.pip ? context.authz.pip.concat(context.authz.path.pip) : context.authz.pip; + context.authz.pdp = context.authz.path.pdp ? context.authz.path.pdp : context.authz.pdp; + context.authz.method = pathandmethod.method; + if(context.authz.method){ + context.authz.pip = context.authz.method.pip ? context.authz.pip.concat(context.authz.method.pip) : context.authz.pip; + context.authz.pdp = context.authz.method.pdp ? context.authz.method.pdp : context.authz.pdp; + } + } + } + log(context, "Leaving protection computation: ") + return context +} diff --git a/roles/postgres/defaults/main.yml b/roles/postgres/defaults/main.yml deleted file mode 100644 index c90ac1a..0000000 --- a/roles/postgres/defaults/main.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -use_jdbc: True -postgres_service_name: 'postgresdb' -postgres_replicas: 1 -conductor_db: postgres -postgres_jdbc_user: conductor -postgres_jdbc_pass: password -postgres_jdbc_db: conductor -postgres_jdbc_url: jdbc:postgresql://{{ postgres_service_name }}:5432/{{ postgres_jdbc_db }} diff --git a/roles/postgres/tasks/main.yaml b/roles/postgres/tasks/main.yaml deleted file mode 100644 index d59d432..0000000 --- a/roles/postgres/tasks/main.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -- name: Generate postgres swarm - template: - src: templates/postgres-swarm.yaml.j2 - dest: "{{ target_path }}/postgres-swarm.yaml" diff --git a/roles/postgres/templates/postgres-swarm.yaml.j2 b/roles/postgres/templates/postgres-swarm.yaml.j2 deleted file mode 100644 index 56d3f56..0000000 --- a/roles/postgres/templates/postgres-swarm.yaml.j2 +++ /dev/null @@ -1,31 +0,0 @@ -version: '3.6' - -services: - - {{ postgres_service_name }}: - image: postgres - ports: - - "5432:5432" - environment: - POSTGRES_USER: "{{ postgres_jdbc_user }}" - POSTGRES_PASSWORD: "{{ postgres_jdbc_pass }}" - POSTGRES_DB: "{{ postgres_jdbc_db }}" -{% if init_db %} - configs: - - source: db-init - target: "/docker-entrypoint-initdb.d/db-init.sql" -{% endif %} - networks: - - {{ conductor_network }} - deploy: - replicas: {{ postgres_replicas }} - placement: - constraints: [node.role == worker] - -networks: - {{ conductor_network }}: -{% if init_db %} -configs: - db-init: - file: {{ target_path }}/conductor-db-init.sql -{% endif %} diff --git a/roles/workers/defaults/main.yaml b/roles/workers/defaults/main.yaml index 6831edd..cdd0835 100644 --- a/roles/workers/defaults/main.yaml +++ b/roles/workers/defaults/main.yaml @@ -1,5 +1,5 @@ --- -conductor_workers_server: http://conductor-dev.int.d4science.net/api +conductor_workers_server: http://conductor-server:8080/api conductor_workers: [ { service: 'base', image: 'nubisware/nubisware-conductor-worker-py-base', replicas: 2, threads: 1, pollrate: 1 }] @@ -8,6 +8,7 @@ pymail_user: "conductor_{{ infrastructure }}" pymail_protocol: "starttls" pymail_port: "587" +#smtp_local_pwd: "" #smtp_dev_pwd: in vault #smtp_pre_pwd: in vault #smtp_prod_pwd: in vault diff --git a/roles/workers/templates/conductor-workers-swarm.yaml.j2 b/roles/workers/templates/conductor-workers-swarm.yaml.j2 index 9183aab..e978a00 100644 --- a/roles/workers/templates/conductor-workers-swarm.yaml.j2 +++ b/roles/workers/templates/conductor-workers-swarm.yaml.j2 @@ -14,12 +14,13 @@ services: deploy: mode: replicated replicas: {{ workers.replicas }} +{% if infrastructure != 'local' %} placement: constraints: [node.role == worker] +{% endif %} restart_policy: condition: on-failure delay: 5s - max_attempts: 3 window: 120s logging: driver: "journald" diff --git a/roles/workers/templates/config.cfg.j2 b/roles/workers/templates/config.cfg.j2 index 21adc3a..6eca2e5 100644 --- a/roles/workers/templates/config.cfg.j2 +++ b/roles/workers/templates/config.cfg.j2 @@ -1,8 +1,8 @@ [common] loglevel = {{ item.get('loglevel', 'info') }} #server = -threads = 3 -pollrate = .1 +threads = 1 +pollrate = 1 {% if "domain" in item.keys() %} domain={{ item.domain }} {% endif %} diff --git a/site-dev.yaml b/site-dev.yaml index 64af8d6..04903f5 100644 --- a/site-dev.yaml +++ b/site-dev.yaml @@ -1,18 +1,25 @@ --- -- hosts: dev_infra +- hosts: dev_infra vars_files: - roles/workers/defaults/smtp.yaml + - roles/pep/defaults/pep_credentials.yaml + - roles/conductor/defaults/conductor_ui_secrets.yaml vars: - cluster_check: true - infrastructure: dev + infrastructure: "dev" pymail_password: "{{ smtp_dev_pwd }}" - conductor_workers_server: http://conductor-server:8080/api + iam_host: https://accounts.dev.d4science.org + pep: True + pep_credentials: "{{ dev_pep_credentials }}" + conductor_ui_secret: "{{ dev_conductor_ui_secret }}" + conductor_auth: oauth2 + conductor_replicas: 2 + conductor_ui_replicas: 2 roles: - common - databases - conductor - workers - - cluster-replacement + - pep tasks: - name: Start {{ db|default('postgres', true) }} and es docker_stack: @@ -36,12 +43,12 @@ - "{{ target_path }}/conductor-swarm.yaml" when: dry is not defined or not dry|bool - - name: Start haproxy + - name: Start pep docker_stack: name: 'conductor-{{ infrastructure }}' state: present compose: - - "{{ target_path }}/haproxy-swarm.yaml" + - "{{ target_path }}/pep-swarm.yaml" when: dry is not defined or not dry|bool - name: Start workers diff --git a/site-local.yaml b/site-local.yaml new file mode 100644 index 0000000..cee5d60 --- /dev/null +++ b/site-local.yaml @@ -0,0 +1,52 @@ +--- +- hosts: localhost + vars_files: + - roles/workers/defaults/smtp.yaml + vars: + infrastructure: "local" + pymail_password: "{{ smtp_local_pwd }}" + smtp_local_pwd: "" + roles: + - common + - databases + - conductor + - workers + - pep + tasks: + - name: Start {{ db|default('postgres', true) }} and es + docker_stack: + name: 'conductor-{{ infrastructure }}' + state: present + compose: + - "{{ target_path }}/{{ db|default('postgres', true) }}-swarm.yaml" + - "{{ target_path }}/elasticsearch-swarm.yaml" + when: dry is not defined or not dry|bool + + - name: Waiting for databases + pause: + seconds: 20 + when: dry is not defined or not dry|bool + + - name: Start conductor + docker_stack: + name: "conductor-{{ infrastructure }}" + state: present + compose: + - "{{ target_path }}/conductor-swarm.yaml" + when: dry is not defined or not dry|bool + + - name: Start pep + docker_stack: + name: 'conductor-{{ infrastructure }}' + state: present + compose: + - "{{ target_path }}/pep-swarm.yaml" + when: dry is not defined or not dry|bool + + - name: Start workers + docker_stack: + name: "conductor-{{ infrastructure }}" + state: present + compose: + - "{{ target_path }}/conductor-workers-swarm.yaml" + when: dry is not defined or not dry|bool diff --git a/site-nw-cluster.yaml b/site-nw-cluster.yaml new file mode 100644 index 0000000..fe12862 --- /dev/null +++ b/site-nw-cluster.yaml @@ -0,0 +1,61 @@ +--- +- hosts: localhost +#- hosts: nw_cluster_infra + vars_files: + - roles/workers/defaults/smtp.yaml + - roles/pep/defaults/pep_credentials.yaml + - roles/conductor/defaults/conductor_ui_secrets.yaml + vars: + infrastructure: "nw-cluster" + pymail_password: "{{ smtp_dev_pwd }}" + iam_host: https://accounts.dev.d4science.org + pep: True + pep_credentials: "{{ nw_cluster_pep_credentials }}" + conductor_ui_secret: "{{ nw_cluster_conductor_ui_secret }}" + conductor_auth: oauth2 + conductor_replicas: 2 + conductor_ui_replicas: 2 + roles: + - common + - databases + - conductor + - workers + - pep + tasks: + - name: Start {{ db|default('postgres', true) }} and es + docker_stack: + name: 'conductor-{{ infrastructure }}' + state: present + compose: + - "{{ target_path }}/{{ db|default('postgres', true) }}-swarm.yaml" + - "{{ target_path }}/elasticsearch-swarm.yaml" + when: dry is not defined or not dry|bool + + - name: Waiting for databases + pause: + seconds: 20 + when: dry is not defined or not dry|bool + + - name: Start conductor + docker_stack: + name: "conductor-{{ infrastructure }}" + state: present + compose: + - "{{ target_path }}/conductor-swarm.yaml" + when: dry is not defined or not dry|bool + + - name: Start pep + docker_stack: + name: 'conductor-{{ infrastructure }}' + state: present + compose: + - "{{ target_path }}/pep-swarm.yaml" + when: dry is not defined or not dry|bool + + - name: Start workers + docker_stack: + name: "conductor-{{ infrastructure }}" + state: present + compose: + - "{{ target_path }}/conductor-workers-swarm.yaml" + when: dry is not defined or not dry|bool diff --git a/site-pre.yaml b/site-pre.yaml index 82c5b13..7b9ca2d 100644 --- a/site-pre.yaml +++ b/site-pre.yaml @@ -1,19 +1,26 @@ --- -- hosts: pre_infra +- hosts: localhost +#- hosts: pre_infra vars_files: - - roles/external-postgres/defaults/vault_main.yaml - roles/workers/defaults/smtp.yaml + - roles/pep/defaults/pep_credentials.yaml + - roles/conductor/defaults/conductor_ui_secrets.yaml vars: - cluster_check: true - infrastructure: pre + infrastructure: "pre" pymail_password: "{{ smtp_pre_pwd }}" - conductor_workers_server: http://conductor-server:8080/api + iam_host: https://accounts.pre.d4science.org + pep: True + pep_credentials: "{{ pre_pep_credentials }}" + conductor_ui_secret: "{{ pre_conductor_ui_secret }}" + conductor_auth: oauth2 + conductor_replicas: 2 + conductor_ui_replicas: 2 roles: - common - - databases - - cluster-replacement + - databases - conductor - workers + - pep tasks: - name: Start {{ db|default('postgres', true) }} and es docker_stack: @@ -26,7 +33,7 @@ - name: Waiting for databases pause: - seconds: 20 + seconds: 20 when: dry is not defined or not dry|bool - name: Start conductor @@ -36,7 +43,15 @@ compose: - "{{ target_path }}/conductor-swarm.yaml" when: dry is not defined or not dry|bool - + + - name: Start pep + docker_stack: + name: 'conductor-{{ infrastructure }}' + state: present + compose: + - "{{ target_path }}/pep-swarm.yaml" + when: dry is not defined or not dry|bool + - name: Start workers docker_stack: name: "conductor-{{ infrastructure }}" diff --git a/site-prod.yaml b/site-prod.yaml index 68e7ef1..cf32cb4 100644 --- a/site-prod.yaml +++ b/site-prod.yaml @@ -1,20 +1,27 @@ --- -- hosts: prod_infra +- hosts: localhost +#- hosts: prod_infra vars_files: - - roles/external-postgres/defaults/vault_main.yaml - roles/workers/defaults/smtp.yaml + - roles/pep/defaults/pep_credentials.yaml + - roles/conductor/defaults/conductor_ui_secrets.yaml vars: - cluster_check: true - conductor_workers_server: http://conductor-server:8080/api + infrastructure: "prod" pymail_password: "{{ smtp_prod_pwd }}" - postgres_jdbc_pass: '{{ jdbc_pass }}' + iam_host: https://accounts.d4science.org + pep: True + pep_credentials: "{{ prod_pep_credentials }}" + conductor_ui_secret: "{{ prod_conductor_ui_secret }}" + conductor_auth: oauth2 + conductor_replicas: 2 + conductor_ui_replicas: 2 roles: - common - - external-postgres - - elasticsearch - - cluster-replacement + - elasticsearch + - external-postgres - conductor - workers + - pep tasks: - name: Start es docker_stack: @@ -36,7 +43,15 @@ compose: - "{{ target_path }}/conductor-swarm.yaml" when: dry is not defined or not dry|bool - + + - name: Start pep + docker_stack: + name: 'conductor-{{ infrastructure }}' + state: present + compose: + - "{{ target_path }}/pep-swarm.yaml" + when: dry is not defined or not dry|bool + - name: Start workers docker_stack: name: "conductor-{{ infrastructure }}" diff --git a/site.yaml b/site.yaml deleted file mode 100644 index 0750d1e..0000000 --- a/site.yaml +++ /dev/null @@ -1,56 +0,0 @@ ---- -- hosts: pre_infra:dev_infra - roles: - - common - - role: cluster-replacement - when: - - cluster_replacement is defined and cluster_replacement|bool - - role: databases - - conductor - - role: workers - when: - - no_workers is not defined or not no_workers|bool - tasks: - - name: Start {{ db|default('postgres', true) }} and es - docker_stack: - name: 'conductor-{{ infrastructure }}' - state: present - compose: - - "{{ target_path }}/{{ db|default('postgres', true) }}-swarm.yaml" - - "{{ target_path }}/elasticsearch-swarm.yaml" - when: dry is not defined or not dry|bool - - - name: Waiting for databases - pause: - seconds: 10 - when: dry is not defined or not dry|bool - - - name: Start conductor - docker_stack: - name: 'conductor-{{ infrastructure }}' - state: present - compose: - - "{{ target_path }}/conductor-swarm.yaml" - when: dry is not defined or not dry|bool - - - name: Start haproxy - docker_stack: - name: 'conductor-{{ infrastructure }}' - state: present - compose: - - "{{ target_path }}/haproxy-swarm.yaml" - when: - - dry is not defined or not dry|bool - - cluster_replacement is defined - - cluster_replacement|bool - - - name: Start workers - docker_stack: - name: 'conductor-{{ infrastructure }}' - state: present - compose: - - "{{ target_path }}/conductor-workers-swarm.yaml" - when: - - dry is not defined or not dry|bool - - no_workers is not defined or not no_workers|bool -