lite-setup: configure lite-server test env

Introduce a script that configures a local testing environment
with ipa default.conf, krb5.conf, and ca.crt from a server hostname.

The lite server configuration allows easy and convenient testing of
IPA server and client code. It uses an existing 389-DS and KRB5 KDC
server on another machine:

    $ contrib/lite-setup.py master.ipa.example
    $ source ~/.ipa/activate.sh
    (ipaenv) $ kinit username
    (ipaenv) $ make lite-server

IPA server UI is available on http://localhost:8888/ipa/

Signed-off-by: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
This commit is contained in:
Christian Heimes 2020-01-17 12:53:31 +01:00 committed by Rob Crittenden
parent 0a55e82d91
commit e9ae7c4b89
3 changed files with 313 additions and 41 deletions

118
contrib/README.md Normal file
View File

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

View File

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

192
contrib/lite-setup.py Executable file
View File

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