conductor 3.0.4 with oauth2 and pep

master
dcore94 3 years ago
parent 2d4585d086
commit 288482d5b6

@ -1,3 +0,0 @@
---
infrastructure: dev
conductor_workers_server: http://conductor-dev.int.d4science.net/api

@ -1,3 +0,0 @@
---
infrastructure: pre
conductor_workers_server: https://conductor.pre.d4science.org/api

@ -1,3 +0,0 @@
---
infrastructure: prod
conductor_workers_server: https://conductor.d4science.org/api

@ -1,5 +1,5 @@
[dev_infra:children]
nw_cluster
dev_cluster
[nw_cluster]
nubis1.int.d4science.net
[dev_cluster]
conductor.dev.d4science.org

@ -0,0 +1,5 @@
[nw_cluster_infra:children]
nw_cluster
[nw_cluster]
nubis1.int.d4science.net

@ -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

@ -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);

@ -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

@ -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

@ -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

@ -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'
}
}
);
}

@ -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/*;
}

@ -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+(?<token>[\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;
}
}

@ -1,4 +0,0 @@
NGINX_PORT=80
CAMUNDA_PORT=8080
PGADMIN_PORT=80
KEYCLOAK_PORT=8080

@ -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"
]
}

@ -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

@ -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

@ -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

@ -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

@ -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 %}

@ -1,2 +0,0 @@
---
haproxy_docker_overlay_network: 'haproxy-public'

@ -1,4 +1,5 @@
---
conductor_server: http://conductor-server:8080/api
target_path: "/tmp/conductor_stack"
conductor_network: conductor-network
conductor_db: postgres

@ -0,0 +1,15 @@
$ANSIBLE_VAULT;1.1;AES256
64626566636365626537356334643266623431393062653538313362663664643538373137383732
3865313539323962666165386336373633303066353634350a633231666363356238326130373561
64336633343066323464343136336333613233396164623537623762323261383537633137363234
3534616537666436370a326331316232613839656436646164343236356233646233623430623665
64663265313964653063333133326636353162353532626364316433373030396434616434333631
32333936666339303963636438616164343063393364666332323831363833323131653666303534
62376162313737303036366532316163383434333130643363613166333433616331393636613635
30613132636261613165613136356638353532663634393431383739363636323961323538383566
62316261373262663335393632376366383031306563343531643632633234346531633164303038
62633039363961613538393832623039383237623663366430313238653030376263613032663437
38386635303332386630386133366232343966393761643635313833316536386634633563326639
38626435393963643866363663353834343333346139363565353161663737393166613938353562
62623661326237353163623138386432376531353864383036613931643164333633646431353162
3666373032663262623438353236626436303132306436326636

@ -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

@ -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"

@ -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 /(.*) *"
]
}

@ -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] %}
conductor.db.type=postgres
conductor.postgres.jdbcUrl={{ postgres_jdbc_url }}
conductor.postgres.jdbcUsername={{ postgres_jdbc_user }}
conductor.postgres.jdbcPassword={{ postgres_jdbc_pass }}
{% 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
# 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

@ -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 %}

@ -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"
]
}

@ -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

@ -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 %}

@ -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 %}

@ -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:

@ -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

@ -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"

@ -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 %}

@ -0,0 +1,2 @@
pep_port: 80
#pep_credentials: in vault

@ -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

@ -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"

@ -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"],
}
]
}
]
}
]
}

@ -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/*;
}

@ -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;
}
}

@ -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;
}
}

@ -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

@ -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
}

@ -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 }}

@ -1,5 +0,0 @@
---
- name: Generate postgres swarm
template:
src: templates/postgres-swarm.yaml.j2
dest: "{{ target_path }}/postgres-swarm.yaml"

@ -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 %}

@ -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

@ -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"

@ -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 %}

@ -2,17 +2,24 @@
- 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

@ -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

@ -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

@ -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
- conductor
- workers
- pep
tasks:
- name: Start {{ db|default('postgres', true) }} and es
docker_stack:
@ -37,6 +44,14 @@
- "{{ 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 }}"

@ -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
- external-postgres
- conductor
- workers
- pep
tasks:
- name: Start es
docker_stack:
@ -37,6 +44,14 @@
- "{{ 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 }}"

@ -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
Loading…
Cancel
Save