diff --git a/opensciencegrid/osdf-chtc-issuer/Dockerfile b/opensciencegrid/osdf-chtc-issuer/Dockerfile
new file mode 100644
index 00000000..a4c8c22b
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/Dockerfile
@@ -0,0 +1,104 @@
+FROM hub.opensciencegrid.org/opensciencegrid/software-base:3.6-el8-release
+
+RUN yum install -y curl java-11-openjdk java-11-openjdk-devel
+
+# Download and install tomcat
+RUN useradd -r -s /sbin/nologin tomcat ;\
+mkdir -p /opt/tomcat ;\
+curl -s -L https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.69/bin/apache-tomcat-9.0.69.tar.gz | tar -zxf - -C /opt/tomcat --strip-components=1 ;\
+chgrp -R tomcat /opt/tomcat/conf ;\
+chmod g+rwx /opt/tomcat/conf ;\
+chmod g+r /opt/tomcat/conf/* ;\
+chown -R tomcat /opt/tomcat/logs/ /opt/tomcat/temp/ /opt/tomcat/webapps/ /opt/tomcat/work/ ;\
+chgrp -R tomcat /opt/tomcat/bin /opt/tomcat/lib ;\
+chmod g+rwx /opt/tomcat/bin ;\
+chmod g+r /opt/tomcat/bin/*
+
+ADD server.xml /opt/tomcat/conf/server.xml
+RUN chgrp -R tomcat /opt/tomcat/conf/server.xml ;\
+chmod go+r /opt/tomcat/conf/server.xml
+
+ARG TOMCAT_ADMIN_USERNAME=admin
+ARG TOMCAT_ADMIN_PASSWORD=password
+ADD tomcat-users.xml.tmpl /opt/tomcat/conf/tomcat-users.xml.tmpl
+RUN sed s+TOMCAT_ADMIN_USERNAME+${TOMCAT_ADMIN_USERNAME}+g /opt/tomcat/conf/tomcat-users.xml.tmpl | sed s+TOMCAT_ADMIN_PASSWORD+${TOMCAT_ADMIN_PASSWORD}+g > /opt/tomcat/conf/tomcat-users.xml ;\
+chgrp tomcat /opt/tomcat/conf/tomcat-users.xml
+
+ARG TOMCAT_ADMIN_IP=127.0.0.1
+ADD manager.xml.tmpl /opt/tomcat/conf/Catalina/localhost/manager.xml.tmpl
+RUN sed s+TOMCAT_ADMIN_IP+${TOMCAT_ADMIN_IP}+g /opt/tomcat/conf/Catalina/localhost/manager.xml.tmpl > /opt/tomcat/conf/Catalina/localhost/manager.xml ;\
+chgrp -R tomcat /opt/tomcat/conf/Catalina
+
+COPY --chown=tomcat:tomcat scitokens-server /opt
+#COPY target/oauth2.war /opt/tomcat/webapps/scitokens-server.war
+RUN \
+curl -s -L https://github.com/ncsa/OA4MP/releases/download/v5.2.9.0/oauth2.war > /opt/tomcat/webapps/scitokens-server.war ;\
+mkdir -p /opt/tomcat/webapps/scitokens-server ;\
+cd /opt/tomcat/webapps/scitokens-server ;\
+jar -xf ../scitokens-server.war ;\
+chgrp -R tomcat /opt/tomcat/webapps/scitokens-server ;\
+mkdir -p /opt/tomcat/var/storage/scitokens-server ;\
+chown -R tomcat:tomcat /opt/tomcat/var/storage/scitokens-server ;\
+rm -rf /opt/tomcat/webapps/ROOT /opt/tomcat/webapps/docs /opt/tomcat/webapps/examples /opt/tomcat/webapps/host-manager /opt/tomcat/webapps/manager
+COPY --chown=tomcat:tomcat scitokens-server/web.xml /opt/tomcat/webapps/scitokens-server/WEB-INF/web.xml
+RUN chmod 644 /opt/tomcat/webapps/scitokens-server/WEB-INF/web.xml
+
+# need to put the java mail jar into the tomcat lib directory
+RUN curl -s -L https://github.com/javaee/javamail/releases/download/JAVAMAIL-1_6_2/javax.mail.jar > /opt/tomcat/lib/javax.mail.jar
+
+# Make JWK a volume mount
+RUN mkdir -p /opt/scitokens-server/bin && mkdir -p /opt/scitokens-server/etc && mkdir -p /opt/scitokens-server/etc/templates && mkdir -p /opt/scitokens-server/lib && mkdir -p /opt/scitokens-server/log && mkdir -p /opt/scitokens-server/var/qdl/scitokens && mkdir -p /opt/scitokens-server/var/storage/file_store
+
+# Make server configuration a volume mount
+ADD scitokens-server/etc/server-config.xml /opt/scitokens-server/etc/server-config.xml.tmpl
+ADD scitokens-server/etc/proxy-config.xml /opt/scitokens-server/etc/proxy-config.xml.tmpl
+
+ADD scitokens-server/bin/scitokens-cli /opt/scitokens-server/bin/scitokens-cli
+#COPY target/oa2-cli.jar /opt/scitokens-server/lib/scitokens-cli.jar
+RUN \
+curl -L -s https://github.com/ncsa/OA4MP/releases/download/v5.2.9.0/oa2-cli.jar >/opt/scitokens-server/lib/scitokens-cli.jar ;\
+chmod +x /opt/scitokens-server/bin/scitokens-cli
+
+ADD scitokens-server/etc/templates/client-template.xml /opt/scitokens-server/etc/templates/client-template.xml
+ADD scitokens-server/var/qdl/scitokens/ospool.qdl /opt/scitokens-server/var/qdl/scitokens/ospool.qdl
+ADD scitokens-server/var/qdl/scitokens/comanage.qdl.tmpl /opt/scitokens-server/var/qdl/scitokens/comanage.qdl.tmpl
+RUN chgrp tomcat /opt/scitokens-server/var/qdl/scitokens/ospool.qdl /opt/scitokens-server/var/qdl/scitokens/comanage.qdl.tmpl
+RUN ln -s /usr/lib64/libapr-1.so.0 /opt/tomcat/lib/libapr-1.so.0
+
+# QDL support 21-01-2021
+RUN curl -L -s https://github.com/ncsa/OA4MP/releases/download/v5.2.9.0/oa2-qdl-installer.jar >/tmp/oa2-qdl-installer.jar ;\
+java -jar /tmp/oa2-qdl-installer.jar -dir /opt/qdl
+
+RUN mkdir -p /opt/qdl/var/scripts
+
+ADD qdl/etc/qdl.properties /opt/qdl/etc/qdl.properties
+ADD qdl/etc/qdl-cfg.xml /opt/qdl/etc/qdl-cfg.xml
+
+ADD qdl/var/scripts/boot.qdl /opt/qdl/var/scripts/boot.qdl
+RUN chmod +x /opt/qdl/var/scripts/boot.qdl
+
+ADD qdl/bin/qdl /opt/qdl/bin/qdl
+RUN chmod +x /opt/qdl/bin/qdl
+
+ADD qdl/bin/qdl-run /opt/qdl/bin/qdl-run
+RUN chmod +x /opt/qdl/bin/qdl-run
+# END QDL support
+
+# Add CHTC custom CA to trust store
+COPY tiger-ca.pem /opt/scitokens-server/tiger-ca.pem
+RUN keytool -import -alias tigerca -file /opt/scitokens-server/tiger-ca.pem -cacerts -trustcacerts -noprompt -storepass changeit;\
+rm /opt/scitokens-server/tiger-ca.pem
+
+ENV JAVA_HOME=/usr/lib/jvm/jre
+ENV CATALINA_PID=/opt/tomcat/temp/tomcat.pid
+ENV CATALINA_HOME=/opt/tomcat
+ENV CATALINA_BASE=/opt/tomcat
+ENV CATALINA_OPTS="-Xms512M -Xmx1024M -server -XX:+UseParallelGC"
+ENV JAVA_OPTS="-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom -Djava.library.path=/opt/tomcat/lib"
+ENV ST_HOME="/opt/scitokens-server"
+ENV QDL_HOME="/opt/qdl"
+ENV PATH="${ST_HOME}/bin:${QDL_HOME}/bin:${PATH}"
+
+#RUN "${QDL_HOME}/var/scripts/boot.qdl"
+ADD start.sh /start.sh
+CMD ["/start.sh"]
diff --git a/opensciencegrid/osdf-chtc-issuer/manager.xml.tmpl b/opensciencegrid/osdf-chtc-issuer/manager.xml.tmpl
new file mode 100644
index 00000000..841abc65
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/manager.xml.tmpl
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/opensciencegrid/osdf-chtc-issuer/qdl/bin/qdl b/opensciencegrid/osdf-chtc-issuer/qdl/bin/qdl
new file mode 100644
index 00000000..6ffeb98d
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/qdl/bin/qdl
@@ -0,0 +1,9 @@
+# The script to invoke the QDL interpreter.
+CFG_FILE="$QDL_HOME/etc/qdl-cfg.xml"
+CFG_NAME="oa2-dev"
+QDL_JAR="$QDL_HOME/lib/qdl.jar"
+
+cfgFile=${1:-$CFG_FILE}
+cfgName=${2:-$CFG_NAME}
+
+java -cp $QDL_JAR edu.uiuc.ncsa.qdl.workspace.QDLWorkspace -cfg $cfgFile -name $cfgName -home_dir $QDL_HOME
\ No newline at end of file
diff --git a/opensciencegrid/osdf-chtc-issuer/qdl/bin/qdl-run b/opensciencegrid/osdf-chtc-issuer/qdl/bin/qdl-run
new file mode 100644
index 00000000..cfd93f36
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/qdl/bin/qdl-run
@@ -0,0 +1,7 @@
+# The script to invoke the QDL interpreter.
+
+CFG_FILE="$QDL_HOME/etc/qdl-cfg.xml"
+CFG_NAME="run-it"
+QDL_JAR="$QDL_HOME/lib/qdl.jar"
+
+java -cp $QDL_JAR edu.uiuc.ncsa.qdl.workspace.QDLWorkspace -cfg $CFG_FILE -name $CFG_NAME -home_dir $QDL_HOME -run "$@"
diff --git a/opensciencegrid/osdf-chtc-issuer/qdl/etc/qdl-cfg.xml b/opensciencegrid/osdf-chtc-issuer/qdl/etc/qdl-cfg.xml
new file mode 100644
index 00000000..548d4f7b
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/qdl/etc/qdl-cfg.xml
@@ -0,0 +1,109 @@
+
+
+
+ /opt/qdl
+ etc/qdl.properties
+
+
+
+
+
+
+
+
+
+ /opt/scitokens-server/var/qdl
+
+ /scripts
+
+
+
+
+ edu.uiuc.ncsa.myproxy.oa4mp.qdl.OA2QDLLoader
+
+
+ edu.uiuc.ncsa.oa2.qdl.QDLToolsLoader
+
+
+ edu.uiuc.ncsa.oa2.qdl.storage.StoreAccessLoader
+
+
+ /opt/qdl/etc/modules/math-x.mdl
+
+
+ /opt/qdl/etc/modules/ext.mdl
+
+
+
+
+
+
+ /opt/qdl
+ etc/qdl.properties
+
+
+
+
+
+ edu.uiuc.ncsa.myproxy.oa4mp.qdl.OA2QDLLoader
+
+
+ edu.uiuc.ncsa.oa2.qdl.QDLToolsLoader
+
+
+ edu.uiuc.ncsa.oa2.qdl.storage.StoreAccessLoader
+
+
+ /opt/qdl/etc/modules/math-x.mdl
+
+
+ /opt/qdl/etc/modules/ext.mdl
+
+
+
+
diff --git a/opensciencegrid/osdf-chtc-issuer/qdl/etc/qdl.properties b/opensciencegrid/osdf-chtc-issuer/qdl/etc/qdl.properties
new file mode 100644
index 00000000..3ed80aa7
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/qdl/etc/qdl.properties
@@ -0,0 +1,2 @@
+#Environment saved to "/opt/qdl/etc/qdl.properties"
+#Basic properties file. This can be empty
diff --git a/opensciencegrid/osdf-chtc-issuer/qdl/nano b/opensciencegrid/osdf-chtc-issuer/qdl/nano
new file mode 100644
index 00000000..faa38886
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/qdl/nano
@@ -0,0 +1 @@
+include /opt/qdl/etc/qdl.nanorc-2.3.1
\ No newline at end of file
diff --git a/opensciencegrid/osdf-chtc-issuer/qdl/var/scripts/boot.qdl b/opensciencegrid/osdf-chtc-issuer/qdl/var/scripts/boot.qdl
new file mode 100644
index 00000000..ffdc4e7e
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/qdl/var/scripts/boot.qdl
@@ -0,0 +1,59 @@
+#! /usr/bin/env qdl-run
+
+/*
+ Boot script in QDL to set up a new OA4MP issuer install. This is run exactly
+ once before the system is started the first time. This will read in all the
+ template files for clients in ${ST_HOME}/etc/templates and ingest them into
+ OA4MP's client store.
+*/
+
+assert[is_defined(os_env().'ST_HOME')]['Environment variable ST_HOME is not defined. Exiting...'];
+
+st_home := os_env().'ST_HOME'; // get the scitokens home directory from the environment
+// normalize the path. If it ends in a /, drop it for later use in strings.
+st_home := '.*/' =~ st_home?substring(st_home,0,size(st_home)-1):st_home;
+template_dir := st_home + '/etc/templates';
+/*
+ Set up access to the client store using the current server configuration.
+*/
+module_import('oa2:/qdl/store', 'clients');
+clients#init(st_home+'/etc/server-config.xml', 'scitokens-server', 'client');
+
+
+files. := dir(template_dir);
+if[
+ size(files.) == 0
+ ][
+ say('(no templates.)');
+ return();
+];
+
+files. := ~mask(files., '.*xml' =~ files.); // regex match on those that end in .xml
+say('processing ' + size(files.) + ' templates from ' + template_dir);
+
+while[
+ for_next(t, files.)
+ ][
+ template. := clients#from_xml(file_read(template_dir + '/' + t));
+ if[
+ !is_defined(template.'client_id')
+ ][
+ say('warning -- file "' + t + '" is not a client template. skipping');
+ ]else[
+ // At this point we don't want to just overwrite an existing template since
+ // there may be customizations that the admin has added.
+ if[
+ size(clients#read(template.'client_id')) == 0
+ ][
+ clients#save(template.);
+ ]else[
+ say('Warning, but "' + t + '" already exists in the store. Update it manually. Skipping');
+ ];
+ ];
+]; // end while
+
+say('done!');
+
+
+
+
diff --git a/opensciencegrid/osdf-chtc-issuer/scitokens-server/bin/create_keys b/opensciencegrid/osdf-chtc-issuer/scitokens-server/bin/create_keys
new file mode 100644
index 00000000..12083855
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/scitokens-server/bin/create_keys
@@ -0,0 +1 @@
+java -jar /opt/scitokens-server/lib/jwt.jar -batch create_keys -single -o
diff --git a/opensciencegrid/osdf-chtc-issuer/scitokens-server/bin/scitokens-cli b/opensciencegrid/osdf-chtc-issuer/scitokens-server/bin/scitokens-cli
new file mode 100644
index 00000000..bdd89a2b
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/scitokens-server/bin/scitokens-cli
@@ -0,0 +1,37 @@
+# Run the OA4MP command processor. This will allow you to edit, create or remove
+# clients, approvals, users and archived users. You can also reset the counter and do copy
+# operations from one store to another
+#
+# The next 5 entries completely determine how this operates. Change these to whatever you want if
+# different from the standard install.
+
+OA2_ROOT=/opt/scitokens-server
+DEFAULT_CONFIG=$OA2_ROOT/etc/server-config.xml
+DEFAULT_TARGET=scitokens-server
+oa2jar=$OA2_ROOT/lib/scitokens-cli.jar
+logFile=$OA2_ROOT/var/log/scitokens--cli.log
+DEFAULT_ENV=$OA2_ROOT/etc/cli.properties
+
+# End of user serviceable parts.
+
+if [[ "$1" = "--help" || $# -gt 2 ]];then
+ echo "scitokens-server-cli [configName configFile environment"]
+ echo "Start the OA4MP for OAuth2 command line admin tool with the"
+ echo "given configuration name in the given configuration file (full path)."
+ echo "No arguments means to use the config named '$DEFAULT_TARGET' in the file '$DEFAULT_CONFIG'"
+ echo "and to try and load the '$DEFAULT_ENV' as the environment."
+ echo "One argument is assumed to be the configuration name in the default config file."
+ exit 1
+fi
+
+target=${1:-$DEFAULT_TARGET}
+adminCfg=${2:-$DEFAULT_CONFIG}
+env=${3:-$DEFAULT_ENV}
+
+java -jar $oa2jar -cfg $adminCfg -name $target -log $logFile -v -set_env $env
+
+if [ $? != 0 ]; then
+ exit 1
+fi
+
+exit 0
diff --git a/opensciencegrid/osdf-chtc-issuer/scitokens-server/etc/proxy-config.xml b/opensciencegrid/osdf-chtc-issuer/scitokens-server/etc/proxy-config.xml
new file mode 100644
index 00000000..2edc7a62
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/scitokens-server/etc/proxy-config.xml
@@ -0,0 +1,24 @@
+
+
+
+ {CLIENT_ID}
+ {CLIENT_SECRET}
+ https://{HOSTNAME}/scitokens-server/ready
+ https://cilogon.org/oauth2
+ https://cilogon.org/authorize
+ https://cilogon.org/oauth2/.well-known/openid-configuration
+
+ email
+ openid
+ profile
+ org.cilogon.userinfo
+
+
+
+
+
\ No newline at end of file
diff --git a/opensciencegrid/osdf-chtc-issuer/scitokens-server/etc/server-config.xml b/opensciencegrid/osdf-chtc-issuer/scitokens-server/etc/server-config.xml
new file mode 100644
index 00000000..e51f734d
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/scitokens-server/etc/server-config.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /opt/scitokens-server/var/qdl
+
+ /scripts
+
+
+
+
+ edu.uiuc.ncsa.myproxy.oa4mp.qdl.OA2QDLLoader
+
+
+
+
+
+
+
diff --git a/opensciencegrid/osdf-chtc-issuer/scitokens-server/etc/templates/client-template.xml b/opensciencegrid/osdf-chtc-issuer/scitokens-server/etc/templates/client-template.xml
new file mode 100644
index 00000000..6ea41958
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/scitokens-server/etc/templates/client-template.xml
@@ -0,0 +1,29 @@
+
+
+
+
+OA4MP stream store
+-1
+4b289478ab9e80f43a837620fd09e3484b10bb77
+2022-01-19T21:39:03.254Z
+1209600000
+{"tokens":{"access":{"audience":"ANY","type":"sci_token","qdl": {"load": "vfs#/scripts/scitokens/ospool.qdl","xmd": {"exec_phase": ["pre_auth","post_token","post_refresh","post_exchange"]}}}}}
+false
+https://localhost:9443/client2
+true
+false
+localhost:template
+false
+true
+["https://localhost:9443/client2/ready"]
+SciToken client template
+2022-01-19T19:55:55.172Z
+-1
+["openid", "wlcg"]
+gaynor@illinois.edu
+-1
+
diff --git a/opensciencegrid/osdf-chtc-issuer/scitokens-server/etc/templates/readme.txt b/opensciencegrid/osdf-chtc-issuer/scitokens-server/etc/templates/readme.txt
new file mode 100644
index 00000000..c2f99219
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/scitokens-server/etc/templates/readme.txt
@@ -0,0 +1,5 @@
+A directory of templates for clients. Each one is in XML format. This directory will be read
+when the system is created and each file will be imported to the store. Simply put
+whatever templates you want in here.
+
+The only requirement to be read is that it end in .xml
\ No newline at end of file
diff --git a/opensciencegrid/osdf-chtc-issuer/scitokens-server/readme.txt b/opensciencegrid/osdf-chtc-issuer/scitokens-server/readme.txt
new file mode 100644
index 00000000..c29ba529
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/scitokens-server/readme.txt
@@ -0,0 +1,38 @@
+Author: Jeff Gaynor
+Created: 2021-08-20T11:12:15.435Z
+
+Originally part of the scitokens/lightweight-issuer project.
+
+This directory has the installation layout and files for the docker image.
+
+╔══════════════════════════════════════════════════════════════════════════╗
+║ NOTE: The permissions for this directory should be restricted to exactly ║
+║ owner - root ║
+║ group - tomcat ║
+╚══════════════════════════════════════════════════════════════════════════╝
+
+If the group is not tomcat, then tomcat cannot access anything in it.
+
+
+Top level for the install is:
+
+/opt/scitokens-server
+
+All files below are relative to that.
+
+web.xml = the web.xml file that should be deployed with this, overwriting the
+ supplied web in the war. This contains the pointer to the configuration
+ and configuring Tomcat as a standalone service.
+
+ etc = configuration files.
+ server-config.xml = the configuration for the OA4MP server.
+ keys.jwk - generated JSON web keys
+
+ log = log files, if logging is enabled.
+
+ var = directory for things that vary.
+ - storage
+ -file_store = file store for OA4MP
+ - qdl
+ -scripts = mounted scripts directory for QDL (OA4MP's policy language).
+
diff --git a/opensciencegrid/osdf-chtc-issuer/scitokens-server/var/qdl/scitokens/comanage.qdl.tmpl b/opensciencegrid/osdf-chtc-issuer/scitokens-server/var/qdl/scitokens/comanage.qdl.tmpl
new file mode 100644
index 00000000..83bfbae2
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/scitokens-server/var/qdl/scitokens/comanage.qdl.tmpl
@@ -0,0 +1,24 @@
+/*
+ * Query the CHTC LDAP for information
+ */
+
+block [
+ ini. := file_read('/opt/secrets/cilogon_ldap_password.ini', 2).'prod';
+
+ ldap_cfg. := new_template('ldap');
+ ldap_cfg.auth_type := 'simple';
+ ldap_cfg.address := ini.'server';
+ ldap_cfg.port := 636;
+ ldap_cfg.claim_name := 'uid';
+ ldap_cfg.search_base := ini.'search_base';
+ ldap_cfg.search_scope := 'subtree';
+ ldap_cfg.fail_on_error := true;
+ ldap_cfg.type := 'ldap';
+ ldap_cfg.groups := ['isMemberOf'];
+ ldap_cfg.ldap_name := 'gecos';
+ ldap_cfg.search_attributes. := ['isMemberOf', 'uid', 'gecos'];
+ ldap_cfg.password := @LDAP_PASSWORD@
+ ldap_cfg.username := ini.'name';
+
+ return(get_claims(create_source(ldap_cfg.), script_args(0)));
+];
diff --git a/opensciencegrid/osdf-chtc-issuer/scitokens-server/var/qdl/scitokens/ospool.qdl b/opensciencegrid/osdf-chtc-issuer/scitokens-server/var/qdl/scitokens/ospool.qdl
new file mode 100644
index 00000000..5cecb22c
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/scitokens-server/var/qdl/scitokens/ospool.qdl
@@ -0,0 +1,89 @@
+/*
+ * This script changes the provided scopes based on request.
+ *
+ * TODO:
+ * - prefix matching, allowing more refined scopes.
+ * - have token prefix path specified by an INI file instead of hardcode.
+ */
+
+if [0 == size(proxy_claims.)] then
+[
+ return();
+];
+
+say(proxy_claims.);
+
+if [!is_defined(proxy_claims.'idp_name')] then
+[
+ sys_err.ok := false;
+ sys_err.message := 'Authentication is missing IDP name.';
+ return();
+];
+
+if [!is_defined(proxy_claims.'eppn')] then
+[
+ sys_err.ok := false;
+ sys_err.message := 'Authentication is missing ePPN.';
+ return();
+];
+
+if [proxy_claims.'idp_name' != 'University of Wisconsin-Madison'] then
+[
+ sys_err.ok := false;
+ sys_err.message := 'Identity provider must be "University of Wisconsin-Madison" for CHTC authentication';
+ return();
+];
+
+eppn_tokens. := tokenize(proxy_claims.'eppn', '@');
+netid := eppn_tokens.0;
+
+record. := script_load('scitokens/comanage.qdl', netid);
+
+if [0 == size(record.)] then
+[
+ sys_err.ok := false;
+ sys_err.message := 'Failed to locate the user record.';
+ return();
+];
+
+group_scopes_read. := [];
+group_scopes_write. := [];
+if [is_defined(record.'isMemberOf'.)] then
+[
+/*
+ if [!reduce(@&&, in_group2('cn=htc_execute_node,ou=user_tags,dc=chtc,dc=wisc,dc=edu', record.'isMemberOf'.))] then
+ [
+ sys_err.ok := false;
+ sys_err.message := 'User must be in the htc_execute_node group';
+ return();
+ ];
+*/
+
+ group_scopes_read. := 'storage.read:/PROTECTED/projects/' + ~values(record.'isMemberOf'.);
+ group_scopes_read. := ~mask(group_scopes_read., -1 >= starts_with(group_scopes_read., 'storage.read:/PROTECTED/projects/cn='));
+ group_scopes_write. := 'storage.write:/PROTECTED/projects/' + ~values(record.'isMemberOf'.);
+ group_scopes_write. := ~mask(group_scopes_write., -1 >= starts_with(group_scopes_write., 'storage.write:/PROTECTED/projects/cn='));
+] else [
+ sys_err.ok := false;
+ sys_err.message := 'User must be in CHTC groups';
+ return();
+];
+
+// group_scopes_read. := ~mask(scopes., -1 < list_starts_with(scopes., group_scopes_read.));
+// group_scopes_write. := ~mask(scopes., -1 < list_starts_with(scopes., group_scopes_write.));
+
+user_scopes. := [];
+if [0 < size(record.uid.)] then
+[
+ //user_scopes. := ~mask(scopes., -1 < list_starts_with(scopes., ['storage.read:/PROTECTED/' + record.uid, 'storage.write:/PROTECTED/' + record.uid]));
+ user_scopes. := ['storage.read:/PROTECTED/' + record.uid, 'storage.write:/PROTECTED/' + record.uid];
+];
+
+remove(access_token.ver.);
+access_token.'wlcg.ver'. := '1.0';
+access_token.'aud' := 'https://wlcg.cern.ch/jwt/v1/any';
+access_token.sub := record.uid;
+access_token.iss := 'https://chtc.cs.wisc.edu';
+
+all_scopes. := unique(group_scopes_read. ~ group_scopes_write. ~ user_scopes.);
+access_token.scope := detokenize(all_scopes., ' ', 2);
diff --git a/opensciencegrid/osdf-chtc-issuer/scitokens-server/var/qdl/scitokens/st.qdl b/opensciencegrid/osdf-chtc-issuer/scitokens-server/var/qdl/scitokens/st.qdl
new file mode 100644
index 00000000..a42871f1
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/scitokens-server/var/qdl/scitokens/st.qdl
@@ -0,0 +1,38 @@
+/*
+ Basic script to fetch the capabilities for a user (by eppn here) and
+ put it in the scopes for the access token.
+*/
+define[
+ stAud(x.)
+ ][
+ aud_caput := 'aud:';
+ xi. := mask(x., starts_with(x., aud_caput) == 0); // only those that start with the caput
+ return(substring(xi., size(aud_caput)));
+ ];
+
+ EPE := 'eduPersonEntitlement';
+
+ cfg. := new_template('file');
+ cfg.'file_path' := 'vfs#/scripts/user-config.txt';
+ /* Uncomment next two lines if you want to enable default user support */
+ cfg.use_default := true;
+ cfg.default_claim := 'default_claim';
+
+ // Snarf up the exactly the EPE from the claims using the subject.
+ eta. := get_claims(create_source(cfg.), claims.'sub');
+
+access_token.scope := detokenize(unique(eta.EPE), ' ', 2); // turn in to string, omit duplications, trailing space
+if[is_defined(eta.'audience')][access_token.'aud' := eta.'audience';];
+xi. := stAud(scopes.);
+if[0
+
+
+ The OA4MP Service
+
+
+ discovery
+ edu.uiuc.ncsa.myproxy.oa4mp.oauth2.servlet.OA2DiscoveryServlet
+
+
+ discovery
+ /.well-known/*
+
+
+ discovery
+ /certs/*
+
+
+
+ callback
+ edu.uiuc.ncsa.oa2.servlet.ProxyCallbackServlet
+ 0
+
+
+ callback
+ /ready
+
+
+
+
+ accessToken
+ edu.uiuc.ncsa.oa2.servlet.OA2ATServlet
+ 0
+
+
+ accessToken
+ /token
+
+
+
+
+ oidc-cm
+ edu.uiuc.ncsa.myproxy.oa4mp.oauth2.cm.oidc_cm.OIDCCMServlet
+
+
+ oidc-cm
+ /oidc-cm
+
+
+
+
+ getCert
+ edu.uiuc.ncsa.myproxy.oa4mp.oauth2.servlet.OA2CertServlet
+
+
+
+ getCert
+ /getcert
+
+
+
+ error
+ edu.uiuc.ncsa.myproxy.oa4mp.server.servlet.ErrorServlet
+
+
+ error
+ /error
+
+
+
+ authorize
+ edu.uiuc.ncsa.oa2.servlet.OA2AuthorizationServer
+
+
+ authorize
+ /authorize
+
+
+
+ device_authorization
+ edu.uiuc.ncsa.oa2.servlet.RFC8628Servlet
+
+
+ device_authorization
+ /device_authorization
+
+
+
+ device
+ edu.uiuc.ncsa.oa2.servlet.RFC8628AuthorizationServer
+
+
+ device
+ /device
+
+
+
+ admin-register
+ edu.uiuc.ncsa.myproxy.oa4mp.oauth2.servlet.OA2AdminRegistrationServlet
+
+
+ admin-register
+ /admin-register
+
+
+
+
+ clientVetting
+ edu.uiuc.ncsa.myproxy.oa4mp.oauth2.servlet.OA2AutoRegistrationServlet
+ 1
+
+
+ clientVetting
+ /register
+
+
+
+ client
+ edu.uiuc.ncsa.myproxy.oa4mp.oauth2.servlet.ClientServlet
+ 1
+
+
+ client
+ /clients
+
+
+
+ userInfo
+ edu.uiuc.ncsa.myproxy.oa4mp.oauth2.servlet.UserInfoServlet
+
+
+ userInfo
+ /userinfo
+
+
+
+ revoke
+ edu.uiuc.ncsa.myproxy.oa4mp.oauth2.servlet.RFC7009
+ 0
+
+
+ revoke
+ /revoke
+
+
+
+ introspect
+ edu.uiuc.ncsa.myproxy.oa4mp.oauth2.servlet.RFC7662
+ 0
+
+
+ introspect
+ /introspect
+
+
+
+
+
+
+ portalSecurity
+ /*
+ GET
+ POST
+
+
+ CONFIDENTIAL
+
+
+
+
+
+
+ Resource reference to a factory for javax.mail.Session
+ instances that may be used for sending electronic mail
+ messages, preconfigured to connect to the appropriate
+ SMTP server.
+
+ mail/Session
+ javax.mail.Session
+ Container
+
+
+
+ edu.uiuc.ncsa.myproxy.oa4mp.oauth2.loader.OA2Bootstrapper
+
+
+
+
+ edu.uiuc.ncsa.myproxy.oa4mp.server.servlet.TooManyRequestsException
+ /tooManyClientRequests.jsp
+
+
+
+ oa4mp:oauth2.server.config.file
+ /opt/scitokens-server/etc/server-config.xml
+
+
+
+
+ oa4mp:oauth2.server.config.name
+ scitokens-server
+
+
+
diff --git a/opensciencegrid/osdf-chtc-issuer/server.xml b/opensciencegrid/osdf-chtc-issuer/server.xml
new file mode 100644
index 00000000..538d5502
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/server.xml
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/opensciencegrid/osdf-chtc-issuer/start.sh b/opensciencegrid/osdf-chtc-issuer/start.sh
new file mode 100755
index 00000000..7a95a5d8
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/start.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+# Set the hostname
+sed s+\{HOSTNAME\}+$HOSTNAME+g /opt/scitokens-server/etc/server-config.xml.tmpl > /opt/scitokens-server/etc/server-config.xml
+sed s+\{HOSTNAME\}+$HOSTNAME+g /opt/scitokens-server/etc/proxy-config.xml.tmpl | \
+sed s+\{CLIENT_ID\}+$CLIENT_ID+g | \
+sed s+\{CLIENT_SECRET\}+$CLIENT_SECRET+g > /opt/scitokens-server/etc/proxy-config.xml
+chgrp tomcat /opt/scitokens-server/etc/server-config.xml
+chgrp tomcat /opt/scitokens-server/etc/proxy-config.xml
+
+# As of OA4MP 5.2.9.0, QDL cannot process an ini file with a '\' -- but it can do so for QDL files.
+export LDAP_PASSWORD=$(cat /opt/secrets/cilogon_ldap_password.ini | grep password | awk '{print $NF}')
+sed s+@LDAP_PASSWORD@+"$(echo "$LDAP_PASSWORD" | sed 's|\\|\\\\\\|')"+ /opt/scitokens-server/var/qdl/scitokens/comanage.qdl.tmpl > /opt/scitokens-server/var/qdl/scitokens/comanage.qdl
+chgrp tomcat /opt/scitokens-server/var/qdl/scitokens/comanage.qdl
+
+# Run the boot to inject the template
+${QDL_HOME}/var/scripts/boot.qdl
+
+# Start tomcat
+exec /opt/tomcat/bin/catalina.sh run
+
diff --git a/opensciencegrid/osdf-chtc-issuer/tiger-ca.pem b/opensciencegrid/osdf-chtc-issuer/tiger-ca.pem
new file mode 100644
index 00000000..ad54f0f0
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/tiger-ca.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDIjCCAgqgAwIBAgIQA/0dKICdYCUtKfaEdwep1DANBgkqhkiG9w0BAQsFADAr
+MQ0wCwYDVQQKEwRjaHRjMRowGAYDVQQDExFUaWdlciBJbnRlcm5hbCBDQTAeFw0y
+MjEyMzEwMDQ5MzZaFw0zMjEyMjgwMDQ5MzZaMCsxDTALBgNVBAoTBGNodGMxGjAY
+BgNVBAMTEVRpZ2VyIEludGVybmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAp89PG8qlaxUosqo77D27SqSofop8nHb3ed1/Vrvuwqgw4b3k2jON
+tCWcWrgixDdx8IJVSnbFkCRMgfuTecLiZ0iePQngXrFpwyKhIkgFfs3/YlVRUpbL
+UUjeiXJRuoBAJpLh2eo5RLHvDe8qNclwQ4JDSxFmljcBKaIZHOerbpvWUin2x1bu
+YX41mNnJ8tWchabOm6L5BcD0d//zW+bQx+s/PK/ohq7GNsmsNgMFWzr6ipD4Osjl
+1uIi62QjdnX0NyplYpFs+2rvXBQcvAwvYb+Wm39yq15rzYi9rN/8+Bbv4DtnF+is
+YZDgsX4dvnoUDJ9/yBTJaorvqUAbED29VwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC
+AqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU5reL6iLsrH/cL54j+QlFRbM0
+FA0wDQYJKoZIhvcNAQELBQADggEBAIKEbJYmjU9FuWTvqX73Sb2K4Axpl/11s4vY
+bnRAj+zmGZavYkYbB/xKPb42BqCjrUCTGVwsn71cUtKzbrFtqnz1lo3WDFkF9gbL
+V+9WebikYlhITrRaEl7sONk7sBe0EG6eOS6dggPzWldzBZ8/z2QwxfKaRwTOdgAK
+9mSJEGJi/VlbExpGn9UyYfD4bMYtpEG+nu4CbnA5dfIjuFjjqCA7HkPd8pxmk/Ir
+IZET7BBwdrOkl+77FiLBBuh7q+UOU1YVZKIvgTE3N4HInFMz4TbIgBLgGsnjg0F8
+Pq52rqAf/WqYAsoaUl5NmNTL2hED4oTkj5STSF8miwvCPcWj/FY=
+-----END CERTIFICATE-----
diff --git a/opensciencegrid/osdf-chtc-issuer/tomcat-users.xml.tmpl b/opensciencegrid/osdf-chtc-issuer/tomcat-users.xml.tmpl
new file mode 100644
index 00000000..aff130f8
--- /dev/null
+++ b/opensciencegrid/osdf-chtc-issuer/tomcat-users.xml.tmpl
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+