Skip to content

Commit

Permalink
feat: admin-ui updated dependencies and manifests (#208)
Browse files Browse the repository at this point in the history
* chore: update dependencies

Overview:
- Alpine upgraded to v3.15
- updated jans-pycloudlib
- Python pip is removed after installing all required libraries

* feat: admin-ui updated dependencies and manifests

* fix: remove admin license params

* docs: update charts docs

Co-authored-by: Mohammad Abudayyeh <47318409+moabu@users.noreply.github.com>
  • Loading branch information
iromli and moabu authored May 6, 2022
1 parent a6ac4df commit 57c5f12
Show file tree
Hide file tree
Showing 13 changed files with 316 additions and 115 deletions.
1 change: 0 additions & 1 deletion automation/janssen_helm_chart/prepare_chart.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ cp -r ./flex-cn-setup/pygluu/kubernetes/templates/helm/gluu/* ${temp_chart_folde
rm ${temp_chart_folder}/openbanking-values.yaml || echo "file doesn't exist"
rm ${temp_chart_folder}/charts/config/templates/upgrade-ldap-101-jans.yaml || echo "file doesn't exist"
rm ${temp_chart_folder}/charts/config/templates/ob-secrets.yaml || echo "file doesn't exist"
rm ${temp_chart_folder}/charts/config/templates/admin-ui-secrets.yaml || echo "file doesn't exist"
rm ${temp_chart_folder}/charts/nginx-ingress/templates/admin-ui-ingress.yaml || echo "file doesn't exist"
rm ${temp_chart_folder}/charts/nginx-ingress/templates/auth-server-protected-ingress.yaml || echo "file doesn't exist"
rm ${temp_chart_folder}/charts/nginx-ingress/templates/casa-ingress.yaml || echo "file doesn't exist"
Expand Down
74 changes: 53 additions & 21 deletions docker-admin-ui/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
FROM node:14.19.1-alpine3.14
FROM node:14.19.1-alpine3.15

# ======
# alpine
# ======

RUN apk update \
&& apk add --no-cache tini openssl py3-pip py3-cryptography py3-psycopg2 nginx \
&& apk add --no-cache --virtual build-deps git openjdk11-jre-headless \
&& apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/v3.15/community py3-grpcio
&& apk upgrade \
&& apk add --no-cache tini openssl python3 py3-cryptography py3-psycopg2 nginx py3-grpcio \
&& apk add --no-cache --virtual .build-deps git openjdk11-jre-headless

# =====
# nginx
Expand All @@ -21,14 +21,51 @@ RUN echo "daemon off;" >> /etc/nginx/nginx.conf
# EXPOSE 80
# EXPOSE 443

# ======
# Python
# ======

COPY requirements.txt /app/requirements.txt
RUN python3 -m ensurepip \
&& pip3 install --no-cache-dir -U pip wheel \
&& pip3 install --no-cache-dir -r /app/requirements.txt \
&& pip3 uninstall -y pip wheel

# =====================
# jans-linux-setup sync
# =====================

ENV JANS_LINUX_SETUP_VERSION=eb113d09421b95671fe1ab4eaa5c4bafc2aed6af
ARG JANS_SETUP_DIR=jans-linux-setup/jans_setup

# note that as we're pulling from a monorepo (with multiple project in it)
# we are using partial-clone and sparse-checkout to get the jans-linux-setup code
RUN git clone --filter blob:none --no-checkout https://github.com/janssenproject/jans /tmp/jans \
&& cd /tmp/jans \
&& git sparse-checkout init --cone \
&& git checkout ${JANS_LINUX_SETUP_VERSION} \
&& git sparse-checkout set ${JANS_SETUP_DIR}

RUN mkdir -p /app/static/rdbm /app/schema

# sync static files from linux-setup
RUN cd /tmp/jans \
&& cp -R ${JANS_SETUP_DIR}/static/rdbm/sql_data_types.json /app/static/rdbm/ \
&& cp -R ${JANS_SETUP_DIR}/static/rdbm/ldap_sql_data_type_mapping.json /app/static/rdbm/ \
&& cp -R ${JANS_SETUP_DIR}/static/rdbm/opendj_attributes_syntax.json /app/static/rdbm/ \
&& cp -R ${JANS_SETUP_DIR}/static/rdbm/sub_tables.json /app/static/rdbm/ \
&& cp ${JANS_SETUP_DIR}/schema/jans_schema.json /app/schema/ \
&& cp ${JANS_SETUP_DIR}/schema/custom_schema.json /app/schema/ \
&& cp ${JANS_SETUP_DIR}/schema/opendj_types.json /app/schema/

# ========
# Admin UI
# ========

# TODO:
# - use NODE_ENV=production
# - download build package (not git clone)
ENV ADMIN_UI_VERSION=7e31ec6f083d0d95e327e2a23c190730227fb212
ENV ADMIN_UI_VERSION=a4c185f2880941e583d2eba5a950fb4021c0f930

# note that as we're pulling from a monorepo (with multiple project in it)
# we are using partial-clone and sparse-checkout to get the admin-ui code
Expand All @@ -39,34 +76,26 @@ RUN git clone --filter blob:none --no-checkout https://github.com/GluuFederation
&& git sparse-checkout set admin-ui \
&& mkdir -p /opt/flex \
&& mv /tmp/flex/admin-ui /opt/flex/admin-ui \
&& rm -rf /tmp/flex \
&& cd /opt/flex/admin-ui \
&& npm install @openapitools/openapi-generator-cli \
&& npm run api \
&& npm install \
&& rm -rf $HOME/.npm

# ======
# Python
# ======

COPY requirements.txt /app/requirements.txt
RUN pip3 install -U pip wheel \
&& pip3 install --no-cache-dir -r /app/requirements.txt

# =======
# Cleanup
# =======

RUN apk del build-deps \
&& rm -rf /var/cache/apk/*
RUN apk del .build-deps \
&& rm -rf /var/cache/apk/* \
&& rm -rf /tmp/jans \
&& rm -rf /tmp/flex

# =======
# License
# =======

RUN mkdir -p /licenses
COPY LICENSE /licenses/
COPY LICENSE /licenses/LICENSE

# ==========
# Config ENV
Expand Down Expand Up @@ -133,6 +162,7 @@ LABEL name="gluufederation/admin-ui" \
COPY templates /app/templates/
COPY scripts /app/scripts/
RUN chmod +x /app/scripts/entrypoint.sh
RUN mkdir -p /etc/jans/conf

# forward logs to stdout and stderr
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
Expand All @@ -145,23 +175,25 @@ RUN addgroup node root
RUN mkdir -p /opt/flex/admin-ui/dist \
&& touch /run/nginx/nginx.pid

# && chown -R 1000:1000 /etc/certs \
# && chgrp -R 0 /etc/certs && chmod -R g=u /etc/certs \

# adjust ownership
RUN chown -R 1000:1000 /var/lib/nginx \
&& chown 1000:1000 /run/nginx/nginx.pid \
&& chown -R 1000:1000 /etc/certs \
&& chown -R 1000:1000 /opt/flex/admin-ui/.env \
&& chown -R 1000:1000 /opt/flex/admin-ui/dist \
&& chown -R 1000:1000 /etc/nginx/http.d/default.conf \
&& chown -R 1000:1000 /var/lib/nginx \
&& chown -R 1000:1000 /var/log/nginx \
&& chown -R 1000:1000 /run/nginx/nginx.pid \
&& chgrp -R 0 /etc/certs && chmod -R g=u /etc/certs \
&& chgrp -R 0 /opt/flex/admin-ui/.env && chmod -R g=u /opt/flex/admin-ui/.env \
&& chgrp -R 0 /opt/flex/admin-ui/dist && chmod -R g=u /opt/flex/admin-ui/dist \
&& chgrp -R 0 /etc/nginx/http.d/default.conf && chmod -R g=u /etc/nginx/http.d/default.conf \
&& chgrp -R 0 /var/lib/nginx && chmod -R g=u /var/lib/nginx \
&& chgrp -R 0 /var/log/nginx && chmod -R g=u /var/log/nginx \
&& chgrp -R 0 /run/nginx/nginx.pid && chmod -R g=u /run/nginx/nginx.pid
&& chgrp -R 0 /run/nginx/nginx.pid && chmod -R g=u /run/nginx/nginx.pid \
&& chgrp -R 0 /etc/jans && chmod -R g=u /etc/jans

USER 1000

Expand Down
7 changes: 7 additions & 0 deletions docker-admin-ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,10 @@ The following environment variables are supported by the container:
- `CN_WAIT_SLEEP_DURATION`: Delay between startup "health checks" (default to `10` seconds).
- `GOOGLE_PROJECT_ID`: Google Project ID (default to empty string). Used when `CN_CONFIG_ADAPTER` or `CN_SECRET_ADAPTER` set to `google`.
- `GOOGLE_APPLICATION_CREDENTIALS`: Path to Google credentials JSON file (default to `/etc/jans/conf/google-credentials.json`). Used when `CN_CONFIG_ADAPTER` or `CN_SECRET_ADAPTER` set to `google`.
- `CN_TOKEN_SERVER_BASE_HOSTNAME`: Hostname of token server (default to `localhost`).
- `CN_TOKEN_SERVER_AUTHZ_ENDPOINT`: Authorization endpoint at token server (default to `/jans-auth/authorize.htm`).
- `CN_TOKEN_SERVER_TOKEN_ENDPOINT`: Token endpoint at token server (default to `/jans-auth/restv1/token`).
- `CN_TOKEN_SERVER_INTROSPECTION_ENDPOINT`: Introspection endpoint at token server (default to `/jans-auth/restv1/introspection`).
- `CN_TOKEN_SERVER_USERINFO_ENDPOINT`: User info endpoint at token server (default to `/jans-auth/restv1/userinfo`).
- `CN_TOKEN_SERVER_CLIENT_ID`: Client ID registered at token server.
- `CN_TOKEN_SERVER_CERT_FILE`: Path to token server certificate (default to `/etc/certs/token_server.crt`).
2 changes: 1 addition & 1 deletion docker-admin-ui/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
libcst<0.4
# pinned to py3-grpcio version to avoid failure on native extension build
grpcio==1.41.0
git+https://github.com/JanssenProject/jans@f88095b1f52f0639221e4109ed7262099e06d0e9#egg=jans-pycloudlib&subdirectory=jans-pycloudlib
git+https://github.com/JanssenProject/jans@f2e653ef917efd017195f2330b64e64c333f4699#egg=jans-pycloudlib&subdirectory=jans-pycloudlib
140 changes: 138 additions & 2 deletions docker-admin-ui/scripts/bootstrap.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
# import os

import logging.config
import os
from uuid import uuid4
from functools import cached_property

from jans.pycloudlib import get_manager
from jans.pycloudlib.utils import get_random_chars
from jans.pycloudlib.utils import encode_text
from jans.pycloudlib.persistence import CouchbaseClient
from jans.pycloudlib.persistence import LdapClient
from jans.pycloudlib.persistence import SpannerClient
from jans.pycloudlib.persistence import SqlClient

from settings import LOGGING_CONFIG

logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("entrypoint")


def render_env(manager):
Expand Down Expand Up @@ -42,6 +55,129 @@ def main():
render_env(manager)
render_nginx_conf(manager)

persistence_setup = PersistenceSetup(manager)
persistence_setup.import_ldif_files()
persistence_setup.export_plugin_properties()


def read_from_file(path):
txt = ""
try:
with open(path) as f:
txt = f.read()
except FileNotFoundError:
logger.warning(f"Unable to read {path} file; fallback to empty string")
return txt.strip()


class PersistenceSetup:
def __init__(self, manager):
self.manager = manager

client_classes = {
"ldap": LdapClient,
"couchbase": CouchbaseClient,
"spanner": SpannerClient,
"sql": SqlClient,
}

# determine persistence type
self.persistence_type = os.environ.get("CN_PERSISTENCE_TYPE", "ldap")
ldap_mapping = os.environ.get("CN_PERSISTENCE_LDAP_MAPPING", "default")

if self.persistence_type == "hybrid":
if ldap_mapping == "default":
client_cls = LdapClient
self.persistence_type = "ldap"
else:
client_cls = CouchbaseClient
self.persistence_type = "couchbase"

# determine persistence client
client_cls = client_classes.get(self.persistence_type)
self.client = client_cls(manager)

def get_token_server_ctx(self):
hostname = os.environ.get("CN_TOKEN_SERVER_BASE_HOSTNAME") or self.manager.config.get("hostname")
authz_endpoint = os.environ.get("CN_TOKEN_SERVER_AUTHZ_ENDPOINT") or "/jans-auth/restv1/authorize"
token_endpoint = os.environ.get("CN_TOKEN_SERVER_TOKEN_ENDPOINT") or "/jans-auth/restv1/token"
introspection_endpoint = os.environ.get("CN_TOKEN_SERVER_INTROSPECTION_ENDPOINT") or "/jans-auth/restv1/introspection"
userinfo_endpoint = os.environ.get("CN_TOKEN_SERVER_USERINFO_ENDPOINT") or "/jans-auth/restv1/userinfo"

pw_file = "/etc/jans/conf/token_server_client_secret"
if not os.path.isfile(pw_file):
self.manager.secret.to_file("token_server_admin_ui_client_pw", pw_file)

ctx = {
"token_server_admin_ui_client_id": os.environ.get("CN_TOKEN_SERVER_CLIENT_ID") or self.manager.config.get("token_server_admin_ui_client_id"),
"token_server_admin_ui_client_pw": read_from_file(pw_file),
"token_server_authz_url": f"https://{hostname}{authz_endpoint}",
"token_server_token_url": f"https://{hostname}{token_endpoint}",
"token_server_introspection_url": f"https://{hostname}{introspection_endpoint}",
"token_server_userinfo_url": f"https://{hostname}{userinfo_endpoint}",
}
return ctx

@cached_property
def ctx(self):
salt = self.manager.secret.get("encoded_salt")

ctx = {
"hostname": self.manager.config.get("hostname"),
}

# admin-ui client for auth server
ctx["admin_ui_client_id"] = self.manager.config.get("admin_ui_client_id")
if not ctx["admin_ui_client_id"]:
ctx["admin_ui_client_id"] = f"1901.{uuid4()}"
self.manager.config.set("admin_ui_client_id", ctx["admin_ui_client_id"])

ctx["admin_ui_client_pw"] = self.manager.secret.get("admin_ui_client_pw")
if not ctx["admin_ui_client_pw"]:
ctx["admin_ui_client_pw"] = get_random_chars()
self.manager.secret.set("admin_ui_client_pw", ctx["admin_ui_client_pw"])

ctx["admin_ui_client_encoded_pw"] = self.manager.secret.get("admin_ui_client_encoded_pw")
if not ctx["admin_ui_client_encoded_pw"]:
ctx["admin_ui_client_encoded_pw"] = encode_text(ctx["admin_ui_client_pw"], salt).decode()
self.manager.secret.set("admin_ui_client_encoded_pw", ctx["admin_ui_client_encoded_pw"])

# admin-ui client for token server
ctx["token_server_admin_ui_client_id"] = self.manager.config.get("token_server_admin_ui_client_id")
if not ctx["token_server_admin_ui_client_id"]:
ctx["token_server_admin_ui_client_id"] = f"1901.{uuid4()}"
self.manager.config.set("token_server_admin_ui_client_id", ctx["token_server_admin_ui_client_id"])

ctx["token_server_admin_ui_client_pw"] = self.manager.secret.get("token_server_admin_ui_client_pw")
if not ctx["token_server_admin_ui_client_pw"]:
ctx["token_server_admin_ui_client_pw"] = get_random_chars()
self.manager.secret.set("token_server_admin_ui_client_pw", ctx["token_server_admin_ui_client_pw"])

ctx["token_server_admin_ui_client_encoded_pw"] = self.manager.secret.get("token_server_admin_ui_client_encoded_pw")
if not ctx["token_server_admin_ui_client_encoded_pw"]:
ctx["token_server_admin_ui_client_encoded_pw"] = encode_text(ctx["token_server_admin_ui_client_pw"], salt).decode()
self.manager.secret.set("token_server_admin_ui_client_encoded_pw", ctx["token_server_admin_ui_client_encoded_pw"])

ctx.update(self.get_token_server_ctx())

# finalized contexts
return ctx

def export_plugin_properties(self):
with open("/app/templates/auiConfiguration.properties.tmpl") as f:
txt = f.read() % self.ctx
self.manager.secret.set("plugins_admin_ui_properties", txt)

@cached_property
def ldif_files(self):
filenames = ["clients.ldif"]
return [f"/app/templates/{filename}" for filename in filenames]

def import_ldif_files(self):
for file_ in self.ldif_files:
logger.info(f"Importing {file_}")
self.client.create_from_ldif(file_, self.ctx)


if __name__ == "__main__":
main()
20 changes: 20 additions & 0 deletions docker-admin-ui/scripts/wait.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import logging
import logging.config
import os

from jans.pycloudlib import get_manager
from jans.pycloudlib import wait_for
from jans.pycloudlib.validators import validate_persistence_type
from jans.pycloudlib.validators import validate_persistence_ldap_mapping
from jans.pycloudlib.validators import validate_persistence_sql_dialect

from settings import LOGGING_CONFIG

Expand All @@ -11,8 +15,24 @@


def main():
persistence_type = os.environ.get("CN_PERSISTENCE_TYPE", "ldap")
validate_persistence_type(persistence_type)

ldap_mapping = os.environ.get("CN_PERSISTENCE_LDAP_MAPPING", "default")
validate_persistence_ldap_mapping(persistence_type, ldap_mapping)

if persistence_type == "sql":
sql_dialect = os.environ.get("CN_SQL_DB_DIALECT", "mysql")
validate_persistence_sql_dialect(sql_dialect)

manager = get_manager()
deps = ["config", "secret"]

if persistence_type == "hybrid":
deps += ["ldap", "couchbase"]
else:
deps.append(persistence_type)

wait_for(manager, deps)


Expand Down
26 changes: 26 additions & 0 deletions docker-admin-ui/templates/auiConfiguration.properties.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# auth server
authserver.clientId=%(admin_ui_client_id)s
authserver.clientSecret=%(admin_ui_client_pw)s
authserver.authzBaseUrl=https://%(hostname)s/jans-auth/restv1/authorize
authserver.scope=openid+profile+email+user_name
authserver.acrValues=basic
authserver.redirectUrl=https://%(hostname)s/admin
authserver.frontChannelLogoutUrl=https://%(hostname)s/admin/logout
authserver.postLogoutRedirectUri=https://%(hostname)s/admin
authserver.tokenEndpoint=https://%(hostname)s/jans-auth/restv1/token
authserver.introspectionEndpoint=https://%(hostname)s/jans-auth/restv1/introspection
authserver.userInfoEndpoint=https://%(hostname)s/jans-auth/restv1/userinfo
authserver.healthCheckUrl=https://%(hostname)s/jans-auth/sys/health-check
authserver.endSessionEndpoint=https://%(hostname)s/jans-auth/restv1/end_session

# token server
tokenServer.clientId=%(token_server_admin_ui_client_id)s
tokenServer.clientSecret=%(token_server_admin_ui_client_pw)s
tokenServer.authzBaseUrl=%(token_server_authz_url)s
tokenServer.scope=openid+profile+email+user_name
tokenServer.acrValues=basic
tokenServer.redirectUrl=https://%(hostname)s/admin
tokenServer.logoutUrl=https://%(hostname)s/admin/logout
tokenServer.tokenEndpoint=%(token_server_token_url)s
tokenServer.introspectionEndpoint=%(token_server_introspection_url)s
tokenServer.userInfoEndpoint=%(token_server_userinfo_url)s
Loading

0 comments on commit 57c5f12

Please sign in to comment.