diff --git a/contrib/README.md b/contrib/README.md new file mode 100644 index 000000000..9bc3006b9 --- /dev/null +++ b/contrib/README.md @@ -0,0 +1,118 @@ +# In-tree development debugging and testing + +lite-server and lite-client enable fast development, debugging, and +performance analysis of server or client code from an in-tree source +directory. The lite-server runs a local web server that uses a remote +LDAP and KRB5 server. + +## Prerequisites + +### Remote IPA server + +Lite-server and lite-client require a running IPA server. The server +should have a similar LDAP schema and IPA version as the in-tree +sources. Some features may not work if the differences are too great. + +The lite-server only needs a working LDAP server and KRB5 server. For +KdcProxy or CA-related features the Apache HTTPd and pki-tomcatd service +must be running, too. + +If the lite-client is configured for remote-server instead of +lite-server, then the lite-client uses the HTTP API of the remote +server. + +### Local setup + +1. Configure and build FreeIPA according to ``BUILD.txt``, TL;DR + +``` +$ sudo dnf builddep -b --spec freeipa.spec.in --best --allowerasing --setopt=install_weak_deps=False +$ ./autogen.sh +$ make +``` + +2. Install additional dependencies for the lite-server + +``` +sudo dnf install -y python3-werkzeug python3-watchdog +``` + +3. The FQDN of the remote IPA server must be resolvable. In case the +server does not have a valid DNS entry, it is possible to add the +hostname and IP address to ``/etc/hosts``. + +4. Create configuration files in ``~/.ipa``. The lite-server requires +an IPA configuration, CA certificate file, KRB5 configuration, +Kerberos TGT and a file based credential cache. The script +``contrib/lite-setup.py`` can create a all necessary files for you +and sets up ``default.conf``, ``krb5.conf``, ``ca.crt``, and +even ``ldap.conf``: + +``` +$ contrib/lite-setup.py master.ipa.example +``` + +5. Setup environment variables: the lite-setup script also creates a +shell source file that activates a virtualenv like environment. The +source files sets several environment variables for PATH, KRB5, LDAP, +IPA, and Python. The env allows you to run the lite server, ``ipa`` +client commands, or OpenLDAP commands: + +``` +$ source ~/.ipa/activate.sh +``` + +4. Acquire a TGT + +``` +(ipaenv) $ kinit username +``` + +5. Run the lite-server + +``` +(ipaenv) $ make lite-server +``` + +6. Run ``ipa`` client commands in another shell session. The lite-setup +scripts provides a wrapper that uses the development sources, too. + +``` +$ source ~/.ipa/activate.sh +(ipaenv) $ which ipa +~/.ipa/ipa +(ipaenv) $ ipa ping +``` + +7. Deactivate the environment + +``` +(ipaenv) $ deactivate_ipaenv +``` + +## Limitations + +The lite-server does not have access to the ra-agent certificate. +Therefore most CA and KRA (vault) operations are not supported. + +## Tricks and tips + +The lite-server has a functional Web UI at +http://localhost:8888/ipa/xml. The session is already authenticated +with the current TGT. + +The lite-setup script has additional options + +* ``--kdcproxy`` configures ``krb5.conf`` for Kerberos over HTTPS +* ``--debug`` enables IPA and KRB5 debugging +* ``--remote-server`` lets you run local client commands without a + local lite-server. + +The ``make lite-server`` command supports arguments like +``PYTHON=/path/to/custom/interpreter`` or +``LITESERVER_ARGS='--enable-profiler=-'``. + +By default the dev server supports HTTP only. To switch to HTTPS, you +can put a PEM file at ~/.ipa/lite.pem. The PEM file must contain a +server certificate, its unencrypted private key and intermediate chain +certs (if applicable). diff --git a/contrib/lite-server.py b/contrib/lite-server.py index 48609cc10..bd73b0763 100755 --- a/contrib/lite-server.py +++ b/contrib/lite-server.py @@ -4,45 +4,7 @@ # """In-tree development server -The dev server requires a Kerberos TGT and a file based credential cache: - - $ mkdir -p ~/.ipa - $ export KRB5CCNAME=~/.ipa/ccache - $ kinit admin - $ make lite-server - -Optionally you can set KRB5_CONFIG to use a custom Kerberos configuration -instead of /etc/krb5.conf. - -To run the lite-server with another Python interpreter: - - $ make lite-server PYTHON=/path/to/bin/python - -To enable profiling: - - $ make lite-server LITESERVER_ARGS='--enable-profiler=-' - -By default the dev server supports HTTP only. To switch to HTTPS, you can put -a PEM file at ~/.ipa/lite.pem. The PEM file must contain a server certificate, -its unencrypted private key and intermediate chain certs (if applicable). - -Prerequisite ------------- - -Additionally to build and runtime requirements of FreeIPA, the dev server -depends on the werkzeug framework and optionally watchdog for auto-reloading. -You may also have to enable a development COPR. - - $ sudo dnf install -y dnf-plugins-core - $ sudo dnf builddep --spec freeipa.spec.in - $ sudo dnf install -y python3-werkzeug python3-watchdog - $ ./autogen.sh - -For more information see - - * http://www.freeipa.org/page/Build - * http://www.freeipa.org/page/Testing - +See README.md for more details. """ import logging import linecache @@ -244,7 +206,7 @@ def init_api(ccname): ) api.finalize() api_time = time.time() - logger.info("API initialized in %03f sec", api_time - start_time) + logger.info("API initialized in %0.3f sec", api_time - start_time) # Validate LDAP connection and pre-fetch schema # Pre-fetching makes the lite-server behave similar to mod_wsgi. werkzeug's @@ -268,7 +230,7 @@ def init_api(ccname): # must have its own connection. ldap2.disconnect() ldap_time = time.time() - logger.info("LDAP schema retrieved %03f sec", ldap_time - api_time) + logger.info("LDAP schema retrieved %0.3f sec", ldap_time - api_time) return api diff --git a/contrib/lite-setup.py b/contrib/lite-setup.py new file mode 100755 index 000000000..d8ca1b229 --- /dev/null +++ b/contrib/lite-setup.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 FreeIPA Contributors see COPYING for license +# +"""Configure lite-server environment. + +See README.md for more details. +""" +import argparse +import os +import socket +from urllib.request import urlopen + +DEFAULT_CONF = """\ +[global] +host = {args.hostname} +server = {args.servername} +basedn = {args.basedn} +realm = {args.realm} +domain = {args.domain} +xmlrpc_uri = {args.xmlrpc_uri} +ldap_uri = ldap://{args.servername} +debug = {args.debug} +enable_ra = False +ra_plugin = dogtag +dogtag_version = 10 +""" + +KRB5_CONF = """\ +[libdefaults] + default_realm = {args.realm} + dns_lookup_realm = false + dns_lookup_kdc = false + rdns = false + ticket_lifetime = 24h + forwardable = true + udp_preference_limit = 0 + default_ccache_name = FILE:{args.ccache} + +[realms] + {args.realm} = {{ + kdc = {args.kdc} + master_kdc = {args.kdc} + admin_server = {args.kadmin} + default_domain = ipa.example + pkinit_anchors = FILE:{args.ca_crt} + pkinit_pool = FILE:{args.ca_crt} + http_anchors = FILE:{args.ca_crt} +}} + +[domain_realm] + .ipa.example = {args.realm} + ipa.example = {args.realm} + {args.servername} = {args.realm} +""" + +LDAP_CONF = """\ +URI ldaps://{args.servername} +BASE {args.basedn} +TLS_CACERT {args.ca_crt} +SASL_MECH GSSAPI +SASL_NOCANON on +""" + +IPA_BIN = """\ +#!/bin/sh +exec python3 -m ipaclient $* +""" + +ACTIVATE = """\ +deactivate_ipaenv () {{ + export PS1="${{_OLD_IPAENV_PS1}}" + export PATH="${{_OLD_IPAENV_PATH}}" + unset _OLD_IPAENV_PS1 + unset _OLD_IPAENV_PATH + unset KRB5_CONFIG + unset KRB5CCNAME + unset LDAPCONF + unset IPA_CONFDIR + unset PYTHONPATH + unset -f deactivate_ipaenv +}} + +export _OLD_IPAENV_PS1="${{PS1:-}}" +export _OLD_IPAENV_PATH="${{PATH:-}}" +export PS1="(ipaenv) ${{PS1:-}}" +export PATH="{args.dot_ipa}:${{PATH:-}}" +export KRB5_CONFIG="{args.krb5_conf}" +export KRB5CCNAME="{args.ccache}" +{args.tracecomment}export KRB5_TRACE=/dev/stderr +export LDAPCONF="{args.ldap_conf}" +export IPA_CONFDIR="{args.dot_ipa}" +export PYTHONPATH="{args.basedir}" +""" + +MSG = """\ +Configured for server '{args.servername}' and realm '{args.realm}'. + +To activate the IPA test env: + + source {args.activate} + kinit + make lite-server + +To deactivate the IPA test env and to unset the env vars: + + deactivate_ipaenv + +The source file configures the env vars: + + export KRB5_CONFIG="{args.krb5_conf}" + export KRB5CCNAME="{args.ccache}" + export LDAPCONF="{args.ldap_conf}" + export IPA_CONFDIR="{args.dot_ipa}" + export PYTHONPATH="{args.basedir}" +""" + +parser = argparse.ArgumentParser() +parser.add_argument("servername", help="IPA server name") +parser.add_argument("domain", default=None, nargs="?") +parser.add_argument( + "--kdcproxy", action="store_true", help="Use KRB5 over HTTPS (KDC-Proxy)" +) +parser.add_argument( + "--debug", + action="store_true", + help="Enable debug mode for lite-server and KRB5", +) +parser.add_argument( + "--remote-server", + action="store_true", + help="Configure client to use a remote server instead of lite-server", +) + + +def main(): + args = parser.parse_args() + if args.domain is None: + args.domain = args.servername.lower().split(".", 1)[1] + else: + args.domain = args.domain.lower().rstrip(".") + args.realm = args.domain.upper() + args.hostname = socket.gethostname() + args.basedn = ",".join(f"dc={part}" for part in args.domain.split(".")) + args.tracecomment = "" if args.debug else "#" + + if args.kdcproxy: + args.kdc = f"https://{args.servername}/KdcProxy" + args.kadmin = f"https://{args.servername}/KdcProxy" + else: + args.kdc = f"{args.servername}:88" + args.kadmin = f"{args.servername}:749" + + if args.remote_server: + args.xmlrpc_uri = f"https://{args.servername}/ipa/xml" + else: + args.xmlrpc_uri = f"http://localhost:8888/ipa/xml" + + args.basedir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + args.dot_ipa = os.path.expanduser("~/.ipa") + args.default_conf = os.path.join(args.dot_ipa, "default.conf") + args.ca_crt = os.path.join(args.dot_ipa, "ca.crt") + args.krb5_conf = os.path.join(args.dot_ipa, "krb5.conf") + args.ldap_conf = os.path.join(args.dot_ipa, "ldap.conf") + args.ccache = os.path.join(args.dot_ipa, "ccache") + args.ipa_bin = os.path.join(args.dot_ipa, "ipa") + args.activate = os.path.join(args.dot_ipa, "activate.sh") + + if not os.path.isdir(args.dot_ipa): + os.makedirs(args.dot_ipa, mode=0o750) + + with urlopen(f"http://{args.servername}/ipa/config/ca.crt") as req: + ca_data = req.read() + with open(args.ca_crt, "wb") as f: + f.write(ca_data) + with open(args.default_conf, "w") as f: + f.write(DEFAULT_CONF.format(args=args)) + with open(args.krb5_conf, "w") as f: + f.write(KRB5_CONF.format(args=args)) + with open(args.ldap_conf, "w") as f: + f.write(LDAP_CONF.format(args=args)) + with open(args.ipa_bin, "w") as f: + f.write(IPA_BIN.format(args=args)) + os.fchmod(f.fileno(), 0o755) + with open(args.activate, "w") as f: + f.write(ACTIVATE.format(args=args)) + + print(MSG.format(args=args)) + + +if __name__ == "__main__": + main()