diff --git a/playbooks/openshift-apps/fasjson.yml b/playbooks/openshift-apps/fasjson.yml
new file mode 100644
index 0000000000..f2f05862c9
--- /dev/null
+++ b/playbooks/openshift-apps/fasjson.yml
@@ -0,0 +1,109 @@
+- name: make the app be real
+ hosts: os_masters[0]:os_masters_stg[0]
+ user: root
+ gather_facts: False
+
+ vars_files:
+ - /srv/web/infra/ansible/vars/global.yml
+ - "/srv/private/ansible/vars.yml"
+ - /srv/web/infra/ansible/vars/{{ ansible_distribution }}.yml
+
+ vars:
+
+ roles:
+ - role: openshift/project
+ app: fasjson
+ description: "Accounts API"
+ appowners:
+ - abompard
+ - pingou
+ tags:
+ - apply-appowners
+ when: env == "production"
+ - role: openshift/project
+ app: fasjson
+ description: "Accounts API"
+ appowners:
+ - abompard
+ - pingou
+ - nils
+ - ryanlerch
+ tags:
+ - apply-appowners
+ when: env == "staging"
+
+ # Declare the service in IPA
+ - role: ipa/service
+ host: "fasjson{{ env_suffix }}.fedoraproject.org"
+ service: HTTP
+
+ # Setup kerberos delegation
+ - role: ipa/servicedelegationtarget
+ name: ipa-http
+ members:
+ - host: {{ ipa_server }}
+ service: HTTP
+ - role: ipa/servicedelegationrule
+ name: fasjson
+ members:
+ - host: "fasjson{{ env_suffix }}.fedoraproject.org"
+ service: HTTP
+ targets:
+ - ipa-http
+ - ipa-ldap
+ # The ipa-ldap delegation target is declared during IPA installation
+
+ # Keytabs
+ - role: openshift/keytab
+ app: fasjson
+ key: host
+ secret_name: fasjson-keytab-host
+ service: host
+ host: "fasjson{{ env_suffix }}.fedoraproject.org"
+ - role: openshift/keytab
+ app: fasjson
+ key: http
+ secret_name: fasjson-keytab-http
+ service: HTTP
+ host: "fasjson{{ env_suffix }}.fedoraproject.org"
+
+ - role: openshift/imagestream
+ app: fasjson
+ imagename: fasjson
+
+ - role: openshift/object
+ app: fasjson
+ template: buildconfig.yml
+ objectname: buildconfig.yml
+
+ - role: openshift/object
+ app: fasjson
+ template: configmap.yml
+ objectname: configmap.yml
+
+ - role: openshift/ipa-client
+ app: fasjson
+
+ - role: openshift/object
+ app: fasjson
+ file: service.yml
+ objectname: service.yml
+
+ - role: openshift/object
+ app: fasjson
+ template: route.yml
+ objectname: route.yml
+
+ - role: openshift/object
+ app: fasjson
+ template: secret-webhook.yml
+ objectname: secret-webhook.yml
+
+ - role: openshift/object
+ app: fasjson
+ template: deploymentconfig.yml
+ objectname: deploymentconfig.yml
+
+ - role: openshift/start-build
+ app: fasjson
+ buildname: fasjson
diff --git a/roles/ipa/servicedelegationrule/defaults/main.yml b/roles/ipa/servicedelegationrule/defaults/main.yml
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/roles/ipa/servicedelegationrule/tasks/main.yml b/roles/ipa/servicedelegationrule/tasks/main.yml
new file mode 100644
index 0000000000..562e772203
--- /dev/null
+++ b/roles/ipa/servicedelegationrule/tasks/main.yml
@@ -0,0 +1,52 @@
+---
+- name: Get admin ticket
+ delegate_to: "{{ ipa_server }}"
+ shell: echo "{{ipa_admin_password}}" | kinit admin
+ check_mode: no
+ changed_when: "1 != 1"
+ tags:
+ - config
+ - krb5
+
+
+- name: Create servicedelegationrule entry
+ delegate_to: "{{ ipa_server }}"
+ command: ipa servicedelegationrule-add {{name}}-delegation
+ register: add_result
+ check_mode: no
+ changed_when: "'Added service delegation rule' in add_result.stdout"
+ failed_when: "not ('Added service delegation rule' in add_result.stdout or 'already exists' in add_result.stderr)"
+ tags:
+ - config
+ - krb5
+
+- name: Add servicedelegationrule members
+ delegate_to: "{{ ipa_server }}"
+ command: ipa servicedelegationrule-add-member {{name}}-delegation --principals={{item.service}}/{{item.host}}@{{ipa_realm}}
+ loop: "{{ members }}"
+ register: add_member_result
+ check_mode: no
+ changed_when: "'Number of members added 1' in add_member_result.stdout"
+ failed_when: "not ('Number of members added 1' in add_member_result.stdout or 'Number of members added 0' in add_member_result.stderr)"
+ tags:
+ - config
+ - krb5
+
+- name: Add servicedelegationrule targets
+ delegate_to: "{{ ipa_server }}"
+ command: ipa servicedelegationrule-add-target {{name}}-delegation --servicedelegationtargets={{item}}-delegation-targets
+ loop: "{{ targets }}"
+ register: add_target_result
+ check_mode: no
+ changed_when: "'Number of members added 1' in add_target_result.stdout"
+ failed_when: "not ('Number of members added 1' in add_target_result.stdout or 'Number of members added 0' in add_target_result.stderr)"
+ tags:
+ - config
+ - krb5
+
+- name: Destroy admin ticket
+ delegate_to: "{{ ipa_server }}"
+ command: kdestroy -A
+ tags:
+ - config
+ - krb5
diff --git a/roles/ipa/servicedelegationtarget/defaults/main.yml b/roles/ipa/servicedelegationtarget/defaults/main.yml
new file mode 100644
index 0000000000..d58c011d07
--- /dev/null
+++ b/roles/ipa/servicedelegationtarget/defaults/main.yml
@@ -0,0 +1 @@
+name: "{{ host }}-{{ service|lower }}"
diff --git a/roles/ipa/servicedelegationtarget/tasks/main.yml b/roles/ipa/servicedelegationtarget/tasks/main.yml
new file mode 100644
index 0000000000..53cd1adc2e
--- /dev/null
+++ b/roles/ipa/servicedelegationtarget/tasks/main.yml
@@ -0,0 +1,40 @@
+---
+- name: Get admin ticket
+ delegate_to: "{{ ipa_server }}"
+ shell: echo "{{ipa_admin_password}}" | kinit admin
+ check_mode: no
+ changed_when: "1 != 1"
+ tags:
+ - config
+ - krb5
+
+
+- name: Create servicedelegationtarget entry
+ delegate_to: "{{ ipa_server }}"
+ command: ipa servicedelegationtarget-add {{name}}-delegation-targets
+ register: add_result
+ check_mode: no
+ changed_when: "'Added service delegation target' in add_result.stdout"
+ failed_when: "not ('Added service delegation target' in add_result.stdout or 'already exists' in add_result.stderr)"
+ tags:
+ - config
+ - krb5
+
+- name: Add servicedelegationtarget members
+ delegate_to: "{{ ipa_server }}"
+ command: ipa servicedelegationtarget-add-member {{name}}-delegation-targets --principals={{item.service}}/{{item.host}}@{{ipa_realm}}
+ loop: "{{ members }}"
+ register: add_member_result
+ check_mode: no
+ changed_when: "'Number of members added 1' in add_member_result.stdout"
+ failed_when: "not ('Number of members added 1' in add_member_result.stdout or 'Number of members added 0' in add_member_result.stderr)"
+ tags:
+ - config
+ - krb5
+
+- name: Destroy admin ticket
+ delegate_to: "{{ ipa_server }}"
+ command: kdestroy -A
+ tags:
+ - config
+ - krb5
diff --git a/roles/openshift-apps/fasjson/files/service.yml b/roles/openshift-apps/fasjson/files/service.yml
new file mode 100644
index 0000000000..7f99e5b13c
--- /dev/null
+++ b/roles/openshift-apps/fasjson/files/service.yml
@@ -0,0 +1,14 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: fasjson-web
+ labels:
+ app: fasjson
+spec:
+ ports:
+ - name: web
+ port: 8080
+ targetPort: 8080
+ selector:
+ app: fasjson
+ deploymentconfig: fasjson
diff --git a/roles/openshift-apps/fasjson/templates/Dockerfile b/roles/openshift-apps/fasjson/templates/Dockerfile
new file mode 100644
index 0000000000..ab9d8699da
--- /dev/null
+++ b/roles/openshift-apps/fasjson/templates/Dockerfile
@@ -0,0 +1,43 @@
+FROM fedora:32
+LABEL \
+ name="fasjson" \
+ vendor="Fedora Infrastructure" \
+ license="GPLv3+"
+ENV HOME=/tmp
+RUN dnf install -y \
+ openldap-clients \
+ vim \
+ git \
+ python3-pip \
+ python3-setuptools \
+ ipa-client \
+ gcc \
+ python-devel \
+ krb5-devel \
+ openldap-devel \
+ httpd \
+ mod_auth_gssapi \
+ mod_session \
+ policycoreutils-python-utils \
+ python3-mod_wsgi \
+ python3-dns \
+ python3-flask \
+ python3-gssapi \
+ python3-ldap \
+ python3-pip \
+ python3-wheel && \
+ dnf autoremove -y && \
+ dnf clean all -y
+RUN git clone https://github.com/fedora-infra/fasjson.git && \
+ pushd fasjson && \
+ git checkout {{ (env == 'production')|ternary('stable', 'staging') }} && \
+ pip-3 install . && \
+ mkdir -p /usr/share/fasjson && \
+ cp ansible/roles/fasjson/files/fasjson.wsgi /usr/share/fasjson && \
+ popd && \
+ rm -rf fasjson
+RUN rm -f /etc/krb5.conf && ln -sf /etc/krb5/krb5.conf /etc/krb5.conf && \
+ ln -sf /etc/keytabs/host /etc/krb5.keytab && \
+ rm -f /etc/openldap/ldap.conf && ln -sf /etc/ipa/ldap.conf /etc/openldap/ldap.conf
+EXPOSE 8080
+ENTRYPOINT bash /etc/fasjson/start.sh
diff --git a/roles/openshift-apps/fasjson/templates/buildconfig.yml b/roles/openshift-apps/fasjson/templates/buildconfig.yml
new file mode 100644
index 0000000000..fa2fc4e9da
--- /dev/null
+++ b/roles/openshift-apps/fasjson/templates/buildconfig.yml
@@ -0,0 +1,30 @@
+{% macro load_file(filename) %}{% include filename %}{%- endmacro -%}
+apiVersion: build.openshift.io/v1
+kind: BuildConfig
+metadata:
+ name: fasjson
+ labels:
+ app: fasjson
+ build: fasjson
+spec:
+ runPolicy: Serial
+ source:
+ type: Dockerfile
+ dockerfile: |-
+ {{ load_file('Dockerfile') | indent(6) }}
+ strategy:
+ type: Docker
+ output:
+ to:
+ kind: ImageStreamTag
+ name: fasjson:latest
+ triggers:
+ - type: ImageChange
+ - type: GitHub
+{% if fasjson_stg_github_secret is defined and env == 'staging' %}
+ github:
+ secret: "{{ fasjson_stg_github_secret }}"
+{% elif fasjson_github_secret is defined and env == 'production' %}
+ github:
+ secret: "{{ fasjson_github_secret }}"
+{% endif %}
diff --git a/roles/openshift-apps/fasjson/templates/configmap.yml b/roles/openshift-apps/fasjson/templates/configmap.yml
new file mode 100644
index 0000000000..4b3beacbda
--- /dev/null
+++ b/roles/openshift-apps/fasjson/templates/configmap.yml
@@ -0,0 +1,26 @@
+{% macro load_file(filename) %}{% include filename %}{%- endmacro -%}
+---
+apiVersion: v1
+kind: List
+metadata: {}
+items:
+- apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: fasjson-config
+ labels:
+ app: fasjson
+ data:
+ start.sh: |-
+ {{ load_file('start.sh') | indent(6) }}
+ httpd.conf: |-
+ {{ load_file('httpd.conf') | indent(6) }}
+- apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: krb5-config
+ labels:
+ app: fasjson
+ data:
+ krb5.conf: |-
+ {{ load_file('krb5.conf') | indent(6) }}
diff --git a/roles/openshift-apps/fasjson/templates/deploymentconfig.yml b/roles/openshift-apps/fasjson/templates/deploymentconfig.yml
new file mode 100644
index 0000000000..51886453ba
--- /dev/null
+++ b/roles/openshift-apps/fasjson/templates/deploymentconfig.yml
@@ -0,0 +1,94 @@
+apiVersion: apps.openshift.io/v1
+kind: DeploymentConfig
+metadata:
+ name: fasjson
+ labels:
+ app: fasjson
+spec:
+ replicas: 1
+ selector:
+ app: fasjson
+ deploymentconfig: fasjson
+ strategy:
+ type: Rolling
+ activeDeadlineSeconds: 21600
+ rollingParams:
+ intervalSeconds: 1
+ maxSurge: 25%
+ maxUnavailable: 25%
+ timeoutSeconds: 600
+ updatePeriodSeconds: 1
+ template:
+ metadata:
+ creationTimestamp: null
+ labels:
+ app: fasjson
+ deploymentconfig: fasjson
+ spec:
+ containers:
+ - name: fasjson
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 8080
+ volumeMounts:
+ - name: keytab-host-volume
+ mountPath: /etc/keytabs/host
+ subPath: host
+ readOnly: true
+ - name: keytab-http-volume
+ mountPath: /etc/keytabs/http
+ subPath: http
+ readOnly: true
+ - name: krb-config-volume
+ mountPath: /etc/krb5
+ readOnly: true
+ - name: fasjson-config-volume
+ mountPath: /etc/fasjson
+ readOnly: true
+ - name: ipa-config-volume
+ mountPath: /etc/ipa
+ readOnly: true
+ - name: httpdir
+ mountPath: /httpdir
+ livenessProbe:
+ timeoutSeconds: 10
+ initialDelaySeconds: 10
+ periodSeconds: 60
+ httpGet:
+ path: /healthz/live
+ port: 8080
+ readinessProbe:
+ timeoutSeconds: 10
+ initialDelaySeconds: 5
+ periodSeconds: 60
+ httpGet:
+ path: /healthz/ready
+ port: 8080
+ volumes:
+ - name: fasjson-config-volume
+ configMap:
+ name: fasjson-config
+ - name: keytab-volume-host
+ secret:
+ secretName: fasjson-keytab-host
+ - name: keytab-volume-http
+ secret:
+ secretName: fasjson-keytab-http
+ - name: krb-config-volume
+ configMap:
+ name: krb5-config
+ - name: ipa-config-volume
+ configMap:
+ name: ipa-config
+ - name: httpdir
+ emptyDir: {}
+ triggers:
+ - imageChangeParams:
+ automatic: true
+ containerNames:
+ - fasjson
+ from:
+ kind: ImageStreamTag
+ name: fasjson:latest
+ type: ImageChange
+ - type: ConfigChange
diff --git a/roles/openshift-apps/fasjson/templates/httpd.conf b/roles/openshift-apps/fasjson/templates/httpd.conf
new file mode 100644
index 0000000000..c953f5f1b8
--- /dev/null
+++ b/roles/openshift-apps/fasjson/templates/httpd.conf
@@ -0,0 +1,95 @@
+Listen 0.0.0.0:8080
+ServerRoot "/httpdir"
+PidFile "/httpdir/httpd.pid"
+LoadModule authn_file_module modules/mod_authn_file.so
+LoadModule authn_anon_module modules/mod_authn_anon.so
+LoadModule authz_user_module modules/mod_authz_user.so
+LoadModule authz_host_module modules/mod_authz_host.so
+LoadModule include_module modules/mod_include.so
+LoadModule log_config_module modules/mod_log_config.so
+LoadModule env_module modules/mod_env.so
+LoadModule ext_filter_module modules/mod_ext_filter.so
+LoadModule expires_module modules/mod_expires.so
+LoadModule headers_module modules/mod_headers.so
+LoadModule mime_module modules/mod_mime.so
+LoadModule status_module modules/mod_status.so
+LoadModule negotiation_module modules/mod_negotiation.so
+LoadModule dir_module modules/mod_dir.so
+LoadModule alias_module modules/mod_alias.so
+LoadModule rewrite_module modules/mod_rewrite.so
+LoadModule version_module modules/mod_version.so
+LoadModule wsgi_module modules/mod_wsgi_python3.so
+LoadModule authn_core_module modules/mod_authn_core.so
+LoadModule authz_core_module modules/mod_authz_core.so
+LoadModule unixd_module modules/mod_unixd.so
+LoadModule mpm_event_module modules/mod_mpm_event.so
+LoadModule request_module modules/mod_request.so
+LoadModule auth_gssapi_module modules/mod_auth_gssapi.so
+LoadModule session_module modules/mod_session.so
+LoadModule session_cookie_module modules/mod_session_cookie.so
+LoadModule session_dbd_module modules/mod_session_dbd.so
+LoadModule auth_form_module modules/mod_auth_form.so
+LoadModule setenvif_module modules/mod_setenvif.so
+
+StartServers 20
+ServerLimit 100
+MaxRequestsPerChild 2000
+MaxRequestWorkers 100
+TypesConfig /etc/mime.types
+AddDefaultCharset UTF-8
+CoreDumpDirectory /tmp
+
+# Logging. Don't log OpenShift's probes
+SetEnvIf Request_URI "^/healthz/" dontlog
+LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
+CustomLog /httpdir/access.log combined env=!dontlog
+ErrorLog /httpdir/error.log
+LogLevel info
+
+WSGISocketPrefix run/wsgi
+WSGIDaemonProcess fasjson processes=4 threads=1 maximum-requests=500 \
+ display-name=%{GROUP} socket-timeout=2147483647 \
+ lang=C.UTF-8 locale=C.UTF-8 home=/httpdir
+WSGIImportScript /usr/share/fasjson/fasjson.wsgi \
+ process-group=fasjson application-group=fasjson
+WSGIScriptAlias / /usr/share/fasjson/fasjson.wsgi
+WSGIScriptReloading Off
+WSGIRestrictStdout Off
+WSGIRestrictSignal Off
+#WSGIPythonOptimize 1 # This causes the ldap module to fail
+
+
+ WSGIProcessGroup fasjson
+ WSGIApplicationGroup fasjson
+
+ Require all granted
+ ErrorDocument 401 /errors/401
+ ErrorDocument 403 /errors/403
+ ErrorDocument 404 /errors/404
+ ErrorDocument 500 /errors/500
+
+
+
+ AuthType GSSAPI
+ AuthName "Kerberos Login"
+ GssapiUseSessions On
+ Session On
+ SessionCookieName ipa_session path=/;httponly;secure;
+ SessionHeader IPASESSION
+ GssapiSessionKey file:/httpdir/run/session.key
+
+ GssapiCredStore keytab:/etc/keytabs/httpd
+ GssapiImpersonate On
+ GssapiDelegCcacheDir /httpdir/run/ccaches
+ GssapiDelegCcachePerms mode:0660
+ GssapiUseS4U2Proxy on
+ GssapiAllowedMech krb5
+
+ Require valid-user
+
+ Header always append X-Frame-Options DENY
+ Header always append Content-Security-Policy "frame-ancestors 'none'"
+ Header unset Set-Cookie
+ Header unset ETag
+ FileETag None
+
diff --git a/roles/openshift-apps/fasjson/templates/krb5.conf b/roles/openshift-apps/fasjson/templates/krb5.conf
new file mode 100644
index 0000000000..fc1d24dcdc
--- /dev/null
+++ b/roles/openshift-apps/fasjson/templates/krb5.conf
@@ -0,0 +1,29 @@
+includedir /etc/krb5.conf.d/
+
+[libdefaults]
+ default_realm = {{ ipa_realm }}
+ dns_lookup_realm = false
+ dns_lookup_kdc = false
+ rdns = false
+ dns_canonicalize_hostname = false
+ ticket_lifetime = 24h
+ forwardable = true
+ udp_preference_limit = 0
+ default_ccache_name = KEYRING:persistent:%{uid}
+
+[realms]
+ {{ ipa_realm }} = {
+ kdc = {{ ipa_server }}:88
+ master_kdc = {{ ipa_server }}:88
+ admin_server = {{ ipa_server }}:749
+ kpasswd_server = {{ ipa_server }}:464
+ default_domain = {{ ipa_realm | lower }}
+ pkinit_anchors = FILE:/etc/ipa/ca.crt
+ pkinit_pool = FILE:/etc/ipa/ca.crt
+ }
+
+[domain_realm]
+ {{ env_suffix }}.fedoraproject.org = {{ ipa_realm }}
+ {{ ipa_realm | lower }} = {{ ipa_realm }}
+ {{ inventory_hostname }} = {{ ipa_realm }}
+ fasjson{{ env_suffix }}.fedoraproject.org = {{ ipa_realm }}
diff --git a/roles/openshift-apps/fasjson/templates/route.yml b/roles/openshift-apps/fasjson/templates/route.yml
new file mode 100644
index 0000000000..2172d3276f
--- /dev/null
+++ b/roles/openshift-apps/fasjson/templates/route.yml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Route
+metadata:
+ name: fasjson-web
+ labels:
+ app: fasjson
+spec:
+ host: fasjson{{ env_suffix }}.fedoraproject.org
+ port:
+ targetPort: web
+ to:
+ kind: Service
+ name: fasjson-web
+ tls:
+ termination: edge
+ insecureEdgeTerminationPolicy: Redirect
diff --git a/roles/openshift-apps/fasjson/templates/secret-webhook.yml b/roles/openshift-apps/fasjson/templates/secret-webhook.yml
new file mode 100644
index 0000000000..da524a1662
--- /dev/null
+++ b/roles/openshift-apps/fasjson/templates/secret-webhook.yml
@@ -0,0 +1,7 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: fasjson-github-webhook-secret
+data:
+ WebHookSecretKey: "{{ (env == 'production')|ternary(fasjson_github_secret, fasjson_stg_github_secret) }}"
+type: Opaque
diff --git a/roles/openshift-apps/fasjson/templates/start.sh b/roles/openshift-apps/fasjson/templates/start.sh
new file mode 100644
index 0000000000..f52aa8b5af
--- /dev/null
+++ b/roles/openshift-apps/fasjson/templates/start.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+rm -rf /httpdir/*
+mkdir /httpdir/run/ /httpdir/run/ccaches/
+ln -s /etc/httpd/modules /httpdir/modules
+truncate --size=0 /httpdir/access.log /httpdir/error.log
+tail -qf /httpdir/access.log /httpdir/error.log &
+exec httpd -f /etc/fasjson/httpd.conf -DFOREGROUND -DNO_DETACH