├── .java-version ├── .sdkmanrc ├── etc └── cas │ ├── .ignore │ └── config │ └── log4j2.xml ├── system.properties ├── .dockerignore ├── Procfile ├── helm ├── delete-cas-server.sh ├── install-cas-server.sh ├── install-cas-server-example.sh ├── cas-server │ ├── templates │ │ ├── casconfig-configmap.yaml │ │ ├── serviceaccount.yaml │ │ ├── role.yaml │ │ ├── rolebinding.yaml │ │ ├── service.yaml │ │ ├── tests │ │ │ └── test-cas-server.yaml │ │ ├── script-configmap.yaml │ │ ├── ingress.yaml │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ └── statefulset.yaml │ ├── .helmignore │ ├── Chart.yaml │ └── values.yaml ├── create-ingress-tls.sh ├── create-cas-server-keystore-secret.sh ├── create-truststore.sh ├── values-example1.yaml └── README.md ├── docker-compose.yml ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── springboot.gradle └── tasks.gradle ├── src └── main │ ├── resources │ ├── application.yml │ └── META-INF │ │ └── spring │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ ├── java │ └── org │ │ └── apereo │ │ └── cas │ │ └── config │ │ └── CasOverlayOverrideConfiguration.java │ └── jib │ └── docker │ └── entrypoint.sh ├── puppeteer ├── package.json ├── scenarios │ └── basic.js └── run.sh ├── settings.gradle ├── .github ├── dependabot.yml ├── renovate.json └── workflows │ └── build.yml ├── .gitattributes ├── lombok.config ├── openrewrite.gradle ├── .gitignore ├── Dockerfile ├── gradlew.bat ├── gradle.properties ├── gradlew ├── LICENSE.txt └── README.md /.java-version: -------------------------------------------------------------------------------- 1 | 25.0 -------------------------------------------------------------------------------- /.sdkmanrc: -------------------------------------------------------------------------------- 1 | java=25 -------------------------------------------------------------------------------- /etc/cas/.ignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=25 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | target/** 2 | build/** 3 | bin/** 4 | .idea/** 5 | .history/** 6 | .github/** 7 | .git/** 8 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: java $JAVA_OPTS -jar build/libs/cas.war --server.port=$PORT --server.ssl.enabled=false 2 | -------------------------------------------------------------------------------- /helm/delete-cas-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | NAMESPACE=${1:-default} 3 | helm delete --namespace "${NAMESPACE}" cas-server -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | cas: 4 | build: . 5 | ports: 6 | - "8443:8443" 7 | - "8080:8080" -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apereo/cas-overlay-template/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /helm/install-cas-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | NAMESPACE=${1:-default} 3 | 4 | helm upgrade --install cas-server --namespace $NAMESPACE ./cas-server -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # Application properties that need to be 2 | # embedded within the web application can be included here 3 | 4 | -------------------------------------------------------------------------------- /puppeteer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "pino-pretty": "13.1.3", 4 | "pino": "10.1.0", 5 | "puppeteer": "24.33.1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | org.apereo.cas.config.CasOverlayOverrideConfiguration 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "org.gradle.toolchains.foojay-resolver-convention" version "${gradleFoojayPluginVersion}" 3 | } 4 | rootProject.name = 'cas' 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gradle 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /helm/install-cas-server-example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | NAMESPACE=${1:-default} 3 | EXAMPLE=${2:-example1} 4 | 5 | helm upgrade --install cas-server --values values-${EXAMPLE}.yaml --namespace ${NAMESPACE} ./cas-server 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set line endings to LF, even on Windows. Otherwise, execution within Docker fails. 2 | # See https://help.github.com/articles/dealing-with-line-endings/ 3 | *.sh text eol=lf 4 | gradlew text eol=lf 5 | *.cmd text eol=crlf 6 | *.bat text eol=crlf 7 | -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | lombok.log.fieldName = LOGGER 2 | lombok.log.fieldIsStatic=true 3 | 4 | lombok.toString.doNotUseGetters=true 5 | lombok.equalsAndHashCode.doNotUseGetters=true 6 | 7 | lombok.addLombokGeneratedAnnotation = true 8 | 9 | config.stopBubbling=true 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":preserveSemverRanges", 5 | ":rebaseStalePrs", 6 | ":disableRateLimiting", 7 | ":semanticCommits", 8 | ":semanticCommitTypeAll(renovatebot)" 9 | ], 10 | "labels": ["dependencies", "bot"] 11 | } 12 | -------------------------------------------------------------------------------- /helm/cas-server/templates/casconfig-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ include "cas-server.fullname" . }}-casconfig 5 | labels: {{- include "cas-server.labels" . | nindent 4 }} 6 | data: 7 | {{- include "cas-server.tplvalues.render" (dict "value" .Values.casServerContainer.casConfig "context" $) | nindent 2 }} 8 | -------------------------------------------------------------------------------- /helm/cas-server/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "cas-server.serviceAccountName" . }} 6 | labels: 7 | {{- include "cas-server.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /helm/cas-server/templates/role.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create -}} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: {{ include "cas-server.fullname" . }} 6 | labels: 7 | {{- include "cas-server.labels" . | nindent 4 }} 8 | rules: 9 | - apiGroups: ["", "extensions", "apps"] 10 | resources: ["configmaps", "pods", "services", "endpoints", "secrets"] 11 | verbs: ["get", "list", "watch"] 12 | {{- end -}} 13 | -------------------------------------------------------------------------------- /helm/cas-server/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /helm/cas-server/templates/rolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create -}} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: RoleBinding 4 | metadata: 5 | name: {{ include "cas-server.fullname" . }} 6 | labels: 7 | {{- include "cas-server.labels" . | nindent 4 }} 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: Role 11 | name: {{ include "cas-server.fullname" . }} 12 | subjects: 13 | - kind: ServiceAccount 14 | name: {{ template "cas-server.serviceAccountName" . }} 15 | namespace: {{ .Release.Namespace }} 16 | {{ end }} 17 | -------------------------------------------------------------------------------- /helm/cas-server/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "cas-server.fullname" . }} 5 | labels: 6 | {{- include "cas-server.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.cas.service.type }} 9 | publishNotReadyAddresses: {{ .Values.cas.service.publishNotReadyAddresses }} 10 | ports: 11 | - port: {{ .Values.cas.service.port }} 12 | targetPort: https 13 | protocol: TCP 14 | name: https 15 | selector: 16 | {{- include "cas-server.selectorLabels" . | nindent 4 }} 17 | -------------------------------------------------------------------------------- /helm/cas-server/templates/tests/test-cas-server.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "cas-server.fullname" . }}-test" 5 | labels: 6 | {{- include "cas-server.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test-success 9 | spec: 10 | containers: 11 | - name: wget 12 | image: alpine 13 | command: ['wget'] 14 | args: [ '--no-check-certificate', 'https://{{ include "cas-server.fullname" . }}:{{ .Values.cas.service.port }}{{ .Values.casServerContainer.defaultStatusUrl }}' ] 15 | restartPolicy: Never 16 | 17 | -------------------------------------------------------------------------------- /openrewrite.gradle: -------------------------------------------------------------------------------- 1 | initscript { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | dependencies { 6 | classpath "org.openrewrite:plugin:7.20.0" 7 | } 8 | } 9 | 10 | rootProject { 11 | plugins.apply(org.openrewrite.gradle.RewritePlugin) 12 | dependencies { 13 | rewrite("org.apereo.cas:cas-server-support-openrewrite:${project.targetVersion}") { 14 | transitive = false 15 | } 16 | } 17 | afterEvaluate { 18 | if (repositories.isEmpty()) { 19 | repositories { 20 | mavenLocal() 21 | mavenCentral() 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ### STS ### 3 | .apt_generated 4 | .classpath 5 | .factorypath 6 | .project 7 | .settings 8 | .springBeans 9 | .sts4-cache 10 | 11 | ### IntelliJ IDEA ### 12 | .idea 13 | *.iws 14 | *.iml 15 | *.ipr 16 | 17 | ### NetBeans ### 18 | /nbproject/private/ 19 | /nbbuild/ 20 | /dist/ 21 | /nbdist/ 22 | /.nb-gradle/ 23 | 24 | ### VS Code ### 25 | .vscode/ 26 | .classpath 27 | !/.project 28 | .project 29 | .settings 30 | .history 31 | .vscode 32 | target/ 33 | .idea/ 34 | .DS_Store 35 | .idea 36 | overlays/ 37 | .gradle/ 38 | build/ 39 | log/ 40 | bin/ 41 | *.war 42 | *.iml 43 | *.log 44 | tmp/ 45 | ./apache-tomcat 46 | apache-tomcat.zip 47 | config-metadata.properties 48 | node-modules 49 | package-lock.json -------------------------------------------------------------------------------- /helm/create-ingress-tls.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | NAMESPACE=${1:-default} 3 | SUBJECT=/CN=cas.example.org/OU=Auth/O=example 4 | SAN=DNS:casadmin.example.org,DNS:cas.example.org 5 | SECRET_NAME=cas-server-ingress-tls 6 | KEY_FILE=cas-ingress.key 7 | CERT_FILE=cas-ingress.crt 8 | 9 | set -e 10 | 11 | # create certificate for external ingress 12 | openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ 13 | -keyout "${KEY_FILE}" -out ${CERT_FILE} -subj "${SUBJECT}" \ 14 | -addext "subjectAltName = $SAN" 15 | 16 | kubectl delete secret "${SECRET_NAME}" --namespace "${NAMESPACE}" || true 17 | # create tls secret with key and cert 18 | kubectl create secret tls "${SECRET_NAME}" --namespace "${NAMESPACE}" --key "${KEY_FILE}" --cert "${CERT_FILE}" 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/org/apereo/cas/config/CasOverlayOverrideConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.apereo.cas.config; 2 | 3 | //import org.springframework.boot.context.properties.EnableConfigurationProperties; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.beans.factory.annotation.Qualifier; 6 | import org.springframework.boot.autoconfigure.AutoConfiguration; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 8 | import org.springframework.context.ConfigurableApplicationContext; 9 | import org.springframework.context.annotation.Bean; 10 | 11 | //import org.apereo.cas.configuration.CasConfigurationProperties; 12 | 13 | @AutoConfiguration 14 | //@EnableConfigurationProperties(CasConfigurationProperties.class) 15 | public class CasOverlayOverrideConfiguration { 16 | 17 | /* 18 | @Bean 19 | public MyCustomBean myCustomBean() { 20 | ... 21 | } 22 | */ 23 | } 24 | -------------------------------------------------------------------------------- /helm/create-cas-server-keystore-secret.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # This script needs bash for pushd/popd 3 | set -e 4 | NAMESPACE=${1:-default} 5 | KEYSTORE=../etc/cas/thekeystore 6 | 7 | # it's important that the service names are supported in the cert used for tomcat in cas-server 8 | # keytool doesn't support wildcards which we really need to use here, e.g. *.cas-server.${NAMESPACE}.svc 9 | # java wasn't resolving using all available dns suffixes so had to use [namespace].svc 10 | SUBJECT=CN=cas.example.org,OU=Example,OU=Org,C=US 11 | SAN=dns:cas.example.org,dns:casadmin.example.org,dns:cas-server-0.cas-server.${NAMESPACE}.svc,dns:cas-server-1.cas-server.${NAMESPACE}.svc 12 | 13 | if [ ! -f "$KEYSTORE" ] ; then 14 | pushd .. 15 | ./gradlew --no-configuration-cache createKeyStore -PcertDir=./etc/cas -PcertificateDn="${SUBJECT}" -PcertificateSubAltName="${SAN}" 16 | popd 17 | fi 18 | 19 | kubectl delete secret cas-server-keystore --namespace "${NAMESPACE}" || true 20 | kubectl create secret generic cas-server-keystore --namespace "${NAMESPACE}" --from-file=thekeystore=$KEYSTORE 21 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | env: 4 | JAVA_OPTS: "-Xms512m -Xmx6048m -Xss128m -XX:ReservedCodeCacheSize=512m -XX:+UseG1GC" 5 | GRADLE_OPTS: "-Xms512m -Xmx6048m -Xss128m -XX:ReservedCodeCacheSize=512m -XX:+UseG1GC" 6 | TERM: xterm-256color 7 | 8 | on: 9 | push: 10 | branches: [ master ] 11 | pull_request: 12 | branches: [ master ] 13 | 14 | concurrency: 15 | group: "workflow = ${{ github.workflow }}, ref = ${{ github.event.ref }}" 16 | cancel-in-progress: ${{ github.event_name == 'push' }} 17 | 18 | jobs: 19 | build: 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | os: [ ubuntu-latest, macos-latest, windows-latest ] 24 | jdk: [ 25 ] 25 | runs-on: ${{ matrix.os }} 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Set up JDK ${{ matrix.jdk }} 29 | uses: actions/setup-java@v4 30 | with: 31 | java-version: ${{ matrix.jdk }} 32 | distribution: 'corretto' 33 | - name: Build with JDK ${{ matrix.jdk }} on ${{ matrix.os }} 34 | run: ./gradlew build 35 | 36 | -------------------------------------------------------------------------------- /helm/cas-server/templates/script-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ include "cas-server.fullname" . }}-scripts 5 | labels: {{- include "cas-server.labels" . | nindent 4 }} 6 | data: 7 | entrypoint.sh: |- 8 | #!/bin/sh 9 | echo Working Directory: $(pwd) 10 | # Set debug options if required 11 | JAVA_DEBUG_ARGS= 12 | if [ "${JAVA_ENABLE_DEBUG}" == "true" ]; then 13 | JAVA_DEBUG_ARGS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=${JAVA_DEBUG_SUSPEND:-n},address=${JAVA_DEBUG_PORT:-5005}" 14 | echo "Run the following to forward local port to pod:" 15 | echo "kubectl port-forward $HOSTNAME ${JAVA_DEBUG_PORT:-5005}:${JAVA_DEBUG_PORT:-5005}" 16 | fi 17 | PROFILE_OPT= 18 | if [ ! -z $CAS_SPRING_PROFILES ]; then 19 | PROFILE_OPT="--spring.profiles.active=$CAS_SPRING_PROFILES" 20 | fi 21 | echo java -server -noverify $JAVA_DEBUG_ARGS $MAX_HEAP_OPT $NEW_HEAP_OPT $JVM_EXTRA_OPTS -jar $CAS_WAR $PROFILE_OPT $@ 22 | exec java -server -noverify $JAVA_DEBUG_ARGS $MAX_HEAP_OPT $NEW_HEAP_OPT $JVM_EXTRA_OPTS -jar $CAS_WAR $PROFILE_OPT $@ 23 | -------------------------------------------------------------------------------- /src/main/jib/docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ENTRYPOINT_DEBUG=${ENTRYPOINT_DEBUG:-false} 4 | JVM_DEBUG=${JVM_DEBUG:-false} 5 | JVM_DEBUG_PORT=${JVM_DEBUG_PORT:-5000} 6 | JVM_DEBUG_SUSPEND=${JVM_DEBUG_SUSPEND:-n} 7 | JVM_MEM_OPTS=${JVM_MEM_OPTS:--Xms512m -Xmx4096M} 8 | JVM_EXTRA_OPTS=${JVM_EXTRA_OPTS:--server -noverify -XX:+TieredCompilation -XX:TieredStopAtLevel=1} 9 | 10 | if [ $JVM_DEBUG = "true" ]; then 11 | JVM_EXTRA_OPTS="${JVM_EXTRA_OPTS} -Xdebug -Xrunjdwp:transport=dt_socket,address=*:${JVM_DEBUG_PORT},server=y,suspend=${JVM_DEBUG_SUSPEND}" 12 | fi 13 | 14 | if [ $ENTRYPOINT_DEBUG = "true" ]; then 15 | JVM_EXTRA_OPTS="${JVM_EXTRA_OPTS} -Ddebug=true" 16 | 17 | echo "\nChecking java..." 18 | java -version 19 | 20 | if [ -d /etc/cas ] ; then 21 | echo "\nListing CAS configuration under /etc/cas..." 22 | ls -R /etc/cas 23 | fi 24 | echo "\nRemote debugger configured on port ${JVM_DEBUG_PORT} with suspend=${JVM_DEBUG_SUSPEND}: ${JVM_DEBUG}" 25 | echo "\nJava args: ${JVM_MEM_OPTS} ${JVM_EXTRA_OPTS}" 26 | fi 27 | 28 | echo "\nRunning CAS @ cas.war" 29 | # shellcheck disable=SC2086 30 | exec java $JVM_EXTRA_OPTS $JVM_MEM_OPTS -jar cas.war "$@" 31 | -------------------------------------------------------------------------------- /helm/cas-server/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: cas-server 3 | description: A Helm chart for CAS SSO Server 4 | icon: "https://apereo.github.io/cas/images/cas_logo.png" 5 | 6 | # A chart can be either an 'application' or a 'library' chart. 7 | # 8 | # Application charts are a collection of templates that can be packaged into versioned archives 9 | # to be deployed. 10 | # 11 | # Library charts provide useful utilities or functions for the chart developer. They're included as 12 | # a dependency of application charts to inject those utilities and functions into the rendering 13 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 14 | type: application 15 | 16 | # This is the chart version. This version number should be incremented each time you make changes 17 | # to the chart and its templates, including the app version. 18 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 19 | version: 0.1.0 20 | 21 | # This is the version number of the application being deployed. This version number should be 22 | # incremented each time you make changes to the application. Versions are not expected to 23 | # follow Semantic Versioning. They should reflect the version the application is using. 24 | appVersion: 'latest' 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE="azul/zulu-openjdk:25" 2 | 3 | FROM $BASE_IMAGE AS overlay 4 | 5 | ARG EXT_BUILD_COMMANDS="" 6 | ARG EXT_BUILD_OPTIONS="" 7 | 8 | ARG JAVA_TOOL_OPTIONS="--enable-native-access=ALL-UNNAMED" 9 | ENV JAVA_TOOL_OPTIONS="${JAVA_TOOL_OPTIONS}" 10 | 11 | WORKDIR /cas-overlay 12 | COPY ./src src/ 13 | COPY ./gradle/ gradle/ 14 | COPY ./gradlew ./settings.gradle ./build.gradle ./gradle.properties ./lombok.config ./ 15 | 16 | RUN mkdir -p ~/.gradle \ 17 | && echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties \ 18 | && echo "org.gradle.configureondemand=true" >> ~/.gradle/gradle.properties \ 19 | && chmod 750 ./gradlew \ 20 | && ./gradlew --version; 21 | 22 | RUN ./gradlew clean build $EXT_BUILD_COMMANDS --parallel --no-daemon -Pexecutable=false $EXT_BUILD_OPTIONS; 23 | 24 | RUN java -Djarmode=tools -jar build/libs/cas.war extract \ 25 | && java -XX:ArchiveClassesAtExit=./cas/cas.jsa -Dspring.context.exit=onRefresh -jar cas/cas.war 26 | 27 | FROM $BASE_IMAGE AS cas 28 | 29 | LABEL "Organization"="Apereo" 30 | LABEL "Description"="Apereo CAS" 31 | 32 | RUN mkdir -p /etc/cas/config \ 33 | && mkdir -p /etc/cas/services \ 34 | && mkdir -p /etc/cas/saml; 35 | 36 | WORKDIR cas-overlay 37 | COPY --from=overlay /cas-overlay/cas cas/ 38 | 39 | COPY etc/cas/ /etc/cas/ 40 | COPY etc/cas/config/ /etc/cas/config/ 41 | COPY etc/cas/services/ /etc/cas/services/ 42 | COPY etc/cas/saml/ /etc/cas/saml/ 43 | 44 | EXPOSE 8080 8443 45 | 46 | ENV PATH $PATH:$JAVA_HOME/bin:. 47 | 48 | ENTRYPOINT ["java", "-server", "-noverify", "-Xmx2048M", "-XX:SharedArchiveFile=cas/cas.jsa", "-jar", "cas/cas.war"] 49 | -------------------------------------------------------------------------------- /helm/cas-server/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.cas.ingress.enabled -}} 2 | {{- $fullName := include "cas-server.fullname" . -}} 3 | {{- $svcPort := .Values.cas.service.port -}} 4 | {{- $kubeVersion := .Capabilities.KubeVersion.Version -}} 5 | {{- if semverCompare ">=1.19.0" $kubeVersion }} 6 | apiVersion: networking.k8s.io/v1 7 | {{- else -}} 8 | apiVersion: networking.k8s.io/v1beta1 9 | {{- end }} 10 | kind: Ingress 11 | metadata: 12 | name: {{ $fullName }} 13 | labels: 14 | {{- include "cas-server.labels" . | nindent 4 }} 15 | {{- with .Values.cas.ingress.annotations }} 16 | annotations: 17 | {{- toYaml . | nindent 4 }} 18 | {{- end }} 19 | spec: 20 | {{- if .Values.cas.ingress.tls }} 21 | tls: 22 | {{- range .Values.cas.ingress.tls }} 23 | - hosts: 24 | {{- range .hosts }} 25 | - {{ . | quote }} 26 | {{- end }} 27 | secretName: {{ .secretName }} 28 | {{- end }} 29 | {{- end }} 30 | rules: 31 | {{- range .Values.cas.ingress.hosts }} 32 | - host: {{ .host | quote }} 33 | http: 34 | paths: 35 | {{- range .paths }} 36 | - path: {{ . }} 37 | {{- if semverCompare ">=1.18.0" $kubeVersion }} 38 | pathType: Prefix 39 | {{- end }} 40 | {{- if semverCompare ">=1.19.0" $kubeVersion }} 41 | backend: 42 | service: 43 | name: {{ $fullName }} 44 | port: 45 | number: {{ $svcPort }} 46 | {{- else }} 47 | backend: 48 | serviceName: {{ $fullName }} 49 | servicePort: {{ $svcPort }} 50 | {{- end }} 51 | {{- end }} 52 | {{- end }} 53 | {{- end }} 54 | -------------------------------------------------------------------------------- /helm/cas-server/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.cas.ingress.enabled }} 3 | {{- range $host := .Values.cas.ingress.hosts }} 4 | {{- range .paths }} 5 | curl -k -v http{{ if $.Values.cas.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}/login 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "cas-server.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "cas-server.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "cas-server.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "cas-server.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | echo "Visit http://127.0.0.1:8080 to use your application" 20 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80 21 | {{- end }} 22 | 23 | Kubernetes Version: {{ .Capabilities.KubeVersion.Version }} 24 | -------------------------------------------------------------------------------- /puppeteer/scenarios/basic.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const assert = require("assert"); 3 | const pino = require('pino'); 4 | const logger = pino({ 5 | level: "info", 6 | transport: { 7 | target: 'pino-pretty' 8 | } 9 | }); 10 | 11 | (async () => { 12 | const browser = await puppeteer.launch({ 13 | headless: (process.env.CI === "true" || process.env.HEADLESS === "true") ? "new" : false, 14 | ignoreHTTPSErrors: true, 15 | devtools: false, 16 | defaultViewport: null, 17 | slowMo: 5, 18 | args: ['--start-maximized', "--window-size=1920,1080"] 19 | }); 20 | 21 | try { 22 | const page = await browser.newPage(); 23 | 24 | const casHost = process.env.PUPPETEER_CAS_HOST || "https://localhost:8443"; 25 | await page.goto(`${casHost}/cas/login`); 26 | 27 | await page.waitForSelector("#username", {visible: true}); 28 | await page.$eval("#username", el => el.value = ''); 29 | await page.type("#username", "casuser"); 30 | 31 | await page.waitForSelector("#password", {visible: true}); 32 | await page.$eval("#password", el => el.value = ''); 33 | await page.type("#password", "Mellon"); 34 | 35 | await page.keyboard.press('Enter'); 36 | await page.waitForNavigation(); 37 | 38 | const cookies = (await page.cookies()).filter(c => { 39 | logger.debug(`Checking cookie ${c.name}:${c.value}`); 40 | return c.name === "TGC"; 41 | }); 42 | assert(cookies.length !== 0); 43 | logger.info(`Cookie:\n${JSON.stringify(cookies, undefined, 2)}`); 44 | await process.exit(0) 45 | } catch (e) { 46 | logger.error(e); 47 | await process.exit(1) 48 | } finally { 49 | await browser.close(); 50 | } 51 | })(); 52 | -------------------------------------------------------------------------------- /helm/create-truststore.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | NAMESPACE=${1:-default} 3 | INGRESS_CERT_FILE=cas-ingress.crt 4 | CAS_CERT_FILE=cas.crt 5 | CAS_KEYSTORE=../etc/cas/thekeystore 6 | TRUST_STORE=../etc/cas/truststore 7 | JAVA_CACERTS=${2:-/etc/ssl/certs/java/cacerts} 8 | 9 | STORE_PASS=changeit 10 | 11 | set -e 12 | 13 | if [ -f ${TRUST_STORE} ]; then 14 | rm ${TRUST_STORE} 15 | fi 16 | 17 | if [ -f "${JAVA_CACERTS}" ]; then 18 | keytool -importkeystore -noprompt -srckeystore "${JAVA_CACERTS}" -srcstorepass "${STORE_PASS}" -destkeystore "${TRUST_STORE}" -deststoretype PKCS12 -deststorepass "${STORE_PASS}" 19 | else 20 | echo "Missing ${JAVA_CACERTS} JAVA_HOME is ${JAVA_HOME}" 21 | if [ -d "${JAVA_HOME}" ]; then 22 | find ${JAVA_HOME} -name cacerts -print 23 | find ${JAVA_HOME} -name cacerts -exec keytool -importkeystore -noprompt -srckeystore {} -srcstorepass "${STORE_PASS}" -destkeystore "${TRUST_STORE}" -deststoretype PKCS12 -deststorepass "${STORE_PASS}" \; 24 | fi 25 | fi 26 | 27 | # create truststore that trusts ingress cert 28 | if [ -f "${INGRESS_CERT_FILE}" ] ; then 29 | keytool -importcert -noprompt -keystore "${TRUST_STORE}" -storepass "${STORE_PASS}" -alias cas-ingress -file "${INGRESS_CERT_FILE}" -storetype PKCS12 30 | else 31 | echo "Missing ingress cert file to put in trust bundle: ${INGRESS_CERT_FILE}" 32 | fi 33 | 34 | # add cas server cert to trust store 35 | if [ -f "${CAS_KEYSTORE}" ] ; then 36 | keytool -exportcert -keystore "${CAS_KEYSTORE}" -storepass "${STORE_PASS}" -alias cas -file "${CAS_CERT_FILE}" -rfc 37 | keytool -importcert -noprompt -storepass "${STORE_PASS}" -keystore "${TRUST_STORE}" -alias cas -file "${CAS_CERT_FILE}" -storetype PKCS12 38 | else 39 | echo "Missing keystore ${CAS_KEYSTORE} to put cas cert in trust bundle" 40 | fi 41 | kubectl delete configmap cas-truststore --namespace "${NAMESPACE}" || true 42 | kubectl create configmap cas-truststore --namespace "${NAMESPACE}" --from-file=truststore=${TRUST_STORE} -------------------------------------------------------------------------------- /puppeteer/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CAS_ARGS="${CAS_ARGS:-}" 4 | 5 | RED="\e[31m" 6 | GREEN="\e[32m" 7 | YELLOW="\e[33m" 8 | ENDCOLOR="\e[0m" 9 | 10 | function printgreen() { 11 | printf "${GREEN}$1${ENDCOLOR}\n" 12 | } 13 | function printyellow() { 14 | printf "${YELLOW}$1${ENDCOLOR}\n" 15 | } 16 | function printred() { 17 | printf "${RED}$1${ENDCOLOR}\n" 18 | } 19 | 20 | casWebApplicationFile="${PWD}/build/libs/cas.war" 21 | if [[ ! -f "$casWebApplicationFile" ]]; then 22 | echo "Building CAS" 23 | ./gradlew clean build -x test -x javadoc --no-configuration-cache --offline 24 | if [ $? -ne 0 ]; then 25 | printred "Failed to build CAS" 26 | exit 1 27 | fi 28 | fi 29 | 30 | if [[ ! -d "${PWD}/puppeteer/node_modules/puppeteer" ]]; then 31 | echo "Installing Puppeteer" 32 | (cd "${PWD}/puppeteer" && npm install puppeteer) 33 | else 34 | echo "Using existing Puppeteer modules..." 35 | fi 36 | 37 | echo -n "NPM version: " && npm --version 38 | echo -n "Node version: " && node --version 39 | 40 | echo "Launching CAS at $casWebApplicationFile with options $CAS_ARGS" 41 | java -jar "$casWebApplicationFile" $CAS_ARGS & 42 | pid=$! 43 | echo "Waiting for CAS under process id ${pid}" 44 | sleep 45 45 | casLogin="${PUPPETEER_CAS_HOST:-https://localhost:8443}/cas/login" 46 | echo "Checking CAS status at ${casLogin}" 47 | curl -k -L --output /dev/null --silent --fail "$casLogin" 48 | if [[ $? -ne 0 ]]; then 49 | printred "Unable to launch CAS instance under process id ${pid}." 50 | printred "Killing process id $pid and exiting" 51 | kill -9 "$pid" 52 | exit 1 53 | fi 54 | 55 | export NODE_TLS_REJECT_UNAUTHORIZED=0 56 | echo "Executing puppeteer scenarios..." 57 | for scenario in "${PWD}"/puppeteer/scenarios/*; do 58 | scenarioName=$(basename "$scenario") 59 | echo "==========================" 60 | echo "- Scenario $scenarioName " 61 | echo -e "==========================\n" 62 | node "$scenario" 63 | rc=$? 64 | echo -e "\n" 65 | if [[ $rc -ne 0 ]]; then 66 | printred "🔥 Scenario $scenarioName FAILED" 67 | else 68 | printgreen "✅ Scenario $scenarioName PASSED" 69 | fi 70 | echo -e "\n" 71 | sleep 1 72 | done; 73 | 74 | kill -9 "$pid" 75 | exit 0 76 | -------------------------------------------------------------------------------- /helm/values-example1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # This is example of a values file that can override and add to the default values.yaml 4 | # Deployers might have one or more values files of their own per deployment environment. 5 | 6 | # CAS Server container properties 7 | casServerContainer: 8 | 9 | # override profiles to include gitsvc 10 | profiles: 'standalone,gitsvc' 11 | 12 | ## Override list of config files from casConfig to mount, include some from default values file 13 | casConfigMounts: 14 | - 'cas.properties' 15 | - 'cas.yaml' 16 | - 'application-gitsvc.yaml' 17 | casConfig: 18 | application-gitsvc.yaml: |- 19 | --- 20 | cas: 21 | service-registry: 22 | git: 23 | repository-url: "{{- .Values.gitsvcRepoUrl -}}" 24 | branches-to-clone: "{{- .Values.gitsvcBranchesToClone -}}" 25 | active-branch: "{{- .Values.gitsvcActiveBranch -}}" 26 | clone-directory: "{{- .Values.gitsvcCloneDirectory -}}" 27 | root-directory: "{{- .Values.gitsvcRootDirectory -}}" 28 | #eof 29 | application-redis.yaml: |- 30 | --- 31 | #helm repo add bitnami https://charts.bitnami.com/bitnami 32 | #helm install cas-server-redis bitnami/redis --set usePassword=false --set sentinel.enabled=true --set sentinel.usePassword=false 33 | cas: 34 | ticket: 35 | registry: 36 | redis: 37 | enabled: true 38 | database: 0 39 | host: 'cas-server-redis' 40 | pool: 41 | test-on-borrow: true 42 | read-from: 'UPSTREAMPREFERRED' 43 | crypto: 44 | enabled: false 45 | timeout: 5000 46 | port: 6379 47 | password: ' ' 48 | cluster: 49 | nodes: 50 | - host: 'cas-server-redis-headless' 51 | port: 6379 52 | password: ' ' 53 | sentinel: 54 | master: 'mymaster' 55 | node: 'cas-server-redis-headless:26379' 56 | # eof 57 | 58 | 59 | gitsvcRepoUrl: 'https://github.com/apereo/cas.git' # need smaller repo with services 60 | gitsvcBranchesToClone: 'master' 61 | gitsvcActiveBranch: 'master' 62 | gitsvcCloneDirectory: '/tmp/cas/services' 63 | gitsvcRootDirectory: 'etc' # only supports one level 64 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /helm/cas-server/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "cas-server.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "cas-server.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "cas-server.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "cas-server.labels" -}} 37 | helm.sh/chart: {{ include "cas-server.chart" . }} 38 | {{ include "cas-server.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "cas-server.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "cas-server.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "cas-server.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "cas-server.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | 64 | {{/* 65 | Return the proper cas-server image name 66 | */}} 67 | {{- define "cas-server.imageName" -}} 68 | {{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} 69 | {{- end -}} 70 | 71 | 72 | {{/* 73 | Return the proper image name (for the init container volume-permissions image) 74 | */}} 75 | {{- define "cas-server.volumePermissions.image" -}} 76 | {{ include "common.images.image" (dict "imageRoot" .Values.volumePermissions.image "global" .Values.global) }} 77 | {{- end -}} 78 | 79 | {{/* 80 | Return the proper image name 81 | {{ include "common.images.image" ( dict "imageRoot" .Values.path.to.the.image "global" $) }} 82 | */}} 83 | {{- define "common.images.image" -}} 84 | {{- $registryName := .imageRoot.registry -}} 85 | {{- $repositoryName := .imageRoot.repository -}} 86 | {{- $tag := default "latest" .imageRoot.tag | toString -}} 87 | {{- if .global }} 88 | {{- if .global.imageRegistry }} 89 | {{- $registryName = .global.imageRegistry -}} 90 | {{- end -}} 91 | {{- end -}} 92 | {{- if ne $registryName "" }} 93 | {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} 94 | {{- else -}} 95 | {{- printf "%s:%s" $repositoryName $tag -}} 96 | {{- end -}} 97 | {{- end -}} 98 | 99 | 100 | {{/* 101 | Return log directory volume 102 | */}} 103 | {{- define "cas-server.logdir" -}} 104 | {{- if .Values.logdir.hostPath -}} 105 | hostPath: 106 | path: {{ .Values.logdir.hostPath }} 107 | type: Directory 108 | {{- else if .Values.logdir.claimName -}} 109 | persistentVolumeClaim: 110 | claimName: {{ .Values.logdir.claimName }} 111 | {{- else -}} 112 | emptyDir: {} 113 | {{- end }} 114 | {{- end -}} 115 | 116 | 117 | {{/* 118 | Renders a value that contains template. 119 | Usage: 120 | {{ include "cas-server.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} 121 | */}} 122 | {{- define "cas-server.tplvalues.render" -}} 123 | {{- if typeIs "string" .value }} 124 | {{- tpl .value .context }} 125 | {{- else }} 126 | {{- tpl (.value | toYaml) .context }} 127 | {{- end }} 128 | {{- end -}} 129 | -------------------------------------------------------------------------------- /helm/README.md: -------------------------------------------------------------------------------- 1 | ## Helm Chart for CAS 2 | 3 | The current helm chart for cas-server demonstrates standing up CAS. 4 | The chart functionality will grow over time, hopefully with contributions from real world deployments. 5 | Eventually it might be nice to support a config-server. 6 | The chart supports mapping in arbitrary volumes and cas config can be specified in values files. 7 | The config could be in cloud config rather than kubernetes config maps, the service registry 8 | could be in a database, git, or a simple json registry in a kubernetes persistent volume. The ticket registry could use a standard helm chart for redis, 9 | postgresql, or mongo, etc. 10 | Currently the chart is attempting to use SSL between ingress controller and the CAS servers. 11 | This is probably overkill and involves all the pain that comes with SSL (e.g. trust & hostname verification). 12 | This chart uses stateful set for CAS rather than a deployment and this may change in the future. 13 | 14 | #### Warning: semver versioning will not be employed until published to a repository. 15 | 16 | ### Install Kubernetes (Docker for Windows/Mac, Minikube, K3S, Rancher, etc) 17 | 18 | - [Docker Desktop](https://www.docker.com/products/docker-desktop) 19 | 20 | - [Minikube](https://minikube.sigs.k8s.io/docs/start/) 21 | 22 | - [k3s](https://k3s.io/) - Works on linux, very light-weight and easy to install for development 23 | ```shell script 24 | curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --disable traefik" sh 25 | # the following export is for helm 26 | export KUBECONFIG=/etc/rancher/k3s/k3s.yaml 27 | ./gradlew clean build jibBuildTar --refresh-dependencies 28 | k3s ctr images import build/jib-image.tar 29 | k3s ctr images ls | grep cas 30 | ./gradlew createKeystore 31 | cd helm 32 | # create secret for tomcat 33 | kubectl create secret generic cas-server-keystore --from-file=thekeystore=/etc/cas/thekeystore 34 | # create secret for ingress controller to use with CAS ingress (nginx-ingress will use default if you don't create) 35 | ./create-ingress-tls.sh 36 | # install cas-server helm chart 37 | helm upgrade --install cas-server ./cas-server 38 | ``` 39 | 40 | ### Install Helm and Kubectl 41 | 42 | Helm v3 and Kubectl are just single binary programs. Kubectl may come with your kubernetes 43 | installation, but you can download both of programs and put them in your path. 44 | - Install [Helm](https://helm.sh/docs/intro/install/) 45 | - Install [Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) 46 | 47 | ### Install ingress controller 48 | 49 | CAS helm chart only tested with Kubernetes ingress-nginx, feel free to add support for other ingress controllers. 50 | 51 | [Kubernetes Nginx Ingress Installation Guide](https://kubernetes.github.io/ingress-nginx/deploy/) 52 | 53 | ### Create secret containing keystore 54 | 55 | Assuming you have run `./gradlew createKeystore` or put you server keystore in `/etc/cas/thekeystore`, 56 | run the following to create a secret containing the keystore: 57 | ```shell script 58 | kubectl create secret generic cas-server-keystore --from-file=thekeystore=/etc/cas/thekeystore 59 | ``` 60 | 61 | ### Install CAS Server helm chart 62 | 63 | Helm charts consist of templates which are combined with values from one or more values files 64 | (and command line set arguments) to produce kubernetes yaml. The templates folder contains a default 65 | values.yaml that is used by default but additional values files can be specified on the command line. 66 | The following examples use the `default` namespace but `--namespace cas` can be added to any resources 67 | created by the helm command to use the specified kubernetes namespace. 68 | ``` 69 | # delete cas-server helm chart install 70 | helm delete cas-server 71 | # install cas-server chart 72 | helm install cas-server ./cas-server 73 | # install or update cas-server 74 | helm upgrade --install cas-server ./cas-server 75 | # use local values file to override defaults 76 | helm upgrade --install cas-server --values values-local.yaml ./cas-server 77 | # see kubernetes yaml without installing 78 | helm upgrade --install cas-server --values values-local.yaml ./cas-server --dry-run --debug 79 | # sometimes dry-run fails b/c yaml can't convert to json so use template instead to see problem 80 | helm template cas-server --values values-local.yaml ./cas-server --debug 81 | ``` 82 | 83 | ### Useful `kubectl` Commands 84 | 85 | ``` 86 | # tail the console logs 87 | kubectl logs cas-server-0 -f 88 | # exec into container 89 | kubectl exec -it cas-server-0 sh 90 | # bounce CAS pod 91 | kubectl delete pod cas-server-0 92 | ``` 93 | 94 | ### Browse to CAS 95 | 96 | Make sure you have host entries for whatever host is listed in values file for this entry: 97 | ``` 98 | ingress: 99 | hosts: 100 | - host: cas.example.org 101 | paths: 102 | - "/cas" 103 | tls: 104 | - secretName: cas-server-ingress-tls 105 | hosts: 106 | - cas.example.org 107 | ``` 108 | 109 | ``` 110 | # host entry 111 | 127.0.0.1 cas.example.org 112 | ``` 113 | Browse to `https://cas.example.org/cas/login` 114 | -------------------------------------------------------------------------------- /gradle/springboot.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "java" 2 | 3 | sourceSets { 4 | bootRunSources { 5 | resources { 6 | srcDirs new File("//etc/cas/templates/"), new File("${project.getProjectDir()}/src/main/resources/") 7 | } 8 | } 9 | } 10 | 11 | configurations { 12 | bootRunConfig { 13 | extendsFrom compileClasspath 14 | 15 | exclude(group: "org.springframework.boot", module: "spring-boot-starter-logging") 16 | exclude(group: "ch.qos.logback", module: "logback-core") 17 | exclude(group: "ch.qos.logback", module: "logback-classic") 18 | } 19 | } 20 | 21 | dependencies { 22 | bootRunConfig "org.apereo.cas:cas-server-core" 23 | bootRunConfig "org.apereo.cas:cas-server-core-logging" 24 | bootRunConfig "org.apereo.cas:cas-server-core-web" 25 | bootRunConfig "org.apereo.cas:cas-server-core-webflow" 26 | bootRunConfig "org.apereo.cas:cas-server-core-cookie" 27 | bootRunConfig "org.apereo.cas:cas-server-core-logout" 28 | bootRunConfig "org.apereo.cas:cas-server-core-authentication" 29 | bootRunConfig "org.apereo.cas:cas-server-core-validation" 30 | bootRunConfig "org.apereo.cas:cas-server-core-audit" 31 | bootRunConfig "org.apereo.cas:cas-server-core-tickets" 32 | bootRunConfig "org.apereo.cas:cas-server-core-services" 33 | bootRunConfig "org.apereo.cas:cas-server-core-util" 34 | 35 | bootRunConfig "org.apereo.cas:cas-server-support-webconfig" 36 | bootRunConfig "org.apereo.cas:cas-server-support-thymeleaf" 37 | bootRunConfig "org.apereo.cas:cas-server-support-validation" 38 | bootRunConfig "org.apereo.cas:cas-server-support-person-directory" 39 | bootRunConfig "org.apereo.cas:cas-server-webapp-resources" 40 | bootRunConfig "org.apereo.cas:cas-server-webapp-init" 41 | bootRunConfig "org.apereo.cas:cas-server-webapp-tomcat" 42 | bootRunConfig "org.apereo.cas:cas-server-webapp-init-tomcat" 43 | 44 | bootRunConfig "org.springframework.cloud:spring-cloud-starter-bootstrap" 45 | bootRunConfig "org.springframework.boot:spring-boot-devtools" 46 | } 47 | 48 | bootRun { 49 | classpath = configurations.bootRunConfig + sourceSets.main.compileClasspath + sourceSets.main.runtimeClasspath 50 | sourceResources sourceSets.bootRunSources 51 | doFirst { 52 | systemProperties = System.properties 53 | } 54 | 55 | def list = [] 56 | list.add("-XX:TieredStopAtLevel=1") 57 | list.add("-Xverify:none") 58 | list.add("--add-modules") 59 | list.add("java.se") 60 | list.add("--add-exports") 61 | list.add("java.base/jdk.internal.ref=ALL-UNNAMED") 62 | list.add("--add-opens") 63 | list.add("java.base/java.lang=ALL-UNNAMED") 64 | list.add("--add-opens") 65 | list.add("java.base/java.nio=ALL-UNNAMED") 66 | list.add("--add-opens") 67 | list.add("java.base/sun.nio.ch=ALL-UNNAMED") 68 | list.add("--add-opens") 69 | list.add("java.management/sun.management=ALL-UNNAMED") 70 | list.add("--add-opens") 71 | list.add("jdk.management/com.sun.management.internal=ALL-UNNAMED") 72 | list.add("-Xrunjdwp:transport=dt_socket,address=5000,server=y,suspend=n") 73 | 74 | jvmArgs = list 75 | 76 | def appArgList = ["--spring.thymeleaf.cache=false"] 77 | args = appArgList 78 | } 79 | 80 | springBoot { 81 | mainClass = "org.apereo.cas.web.CasWebApplication" 82 | 83 | } 84 | 85 | tasks.cyclonedxDirectBom { 86 | xmlOutput.unsetConvention() 87 | jsonOutput.set(file("$buildDir/META-INF/sbom/application.cdx")) 88 | } 89 | tasks.cyclonedxBom { 90 | xmlOutput.unsetConvention() 91 | jsonOutput.set(file("$buildDir/META-INF/sbom/application.cdx")) 92 | } 93 | 94 | bootWar { 95 | dependsOn cyclonedxBom 96 | 97 | archiveFileName = "cas.war" 98 | archiveBaseName = "cas" 99 | 100 | entryCompression = ZipEntryCompression.STORED 101 | 102 | /* 103 | attachClasses = true 104 | classesClassifier = 'classes' 105 | archiveClasses = true 106 | */ 107 | 108 | from("$buildDir/META-INF/sbom") { 109 | into "META-INF/sbom" 110 | } 111 | 112 | overlays { 113 | /* 114 | https://docs.freefair.io/gradle-plugins/current/reference/#_io_freefair_war_overlay 115 | Note: The "excludes" property is only for files in the war dependency. 116 | If a jar is excluded from the war, it could be brought back into the final war as a dependency 117 | of non-war dependencies. Those should be excluded via normal gradle dependency exclusions. 118 | */ 119 | cas { 120 | from "org.apereo.cas:cas-server-webapp${project.findProperty('appServer') ?: ''}:${project.'cas.version'}@war" 121 | 122 | 123 | provided = false 124 | 125 | def excludeArtifacts = ["WEB-INF/lib/servlet-api-2*.jar"] 126 | if (project.hasProperty("tomcatVersion")) { 127 | excludeArtifacts += "WEB-INF/lib/tomcat-*.jar" 128 | } 129 | excludes = excludeArtifacts 130 | 131 | /* 132 | excludes = ["WEB-INF/lib/somejar-1.0*"] 133 | enableCompilation = true 134 | includes = ["*.xyz"] 135 | targetPath = "sub-path/bar" 136 | skip = false 137 | */ 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # This overlay project's version 2 | # For consistency and with no other effect, this is set to the CAS version itself. 3 | version=8.0.0-SNAPSHOT 4 | 5 | # This is the CAS server version that will be deployed. 6 | # Versions typically are in the format of: 7 | # [Major].[Minor].[Patch].[Security] 8 | # For patch and security releases and unless explicitly stated otherwise, the below property 9 | # and NOTHING ELSE is the only change you usually need to make to upgrade your CAS deployment. 10 | cas.version=8.0.0-SNAPSHOT 11 | 12 | # The Spring Boot version is very much tied to the CAS release 8.0.0-SNAPSHOT 13 | # and must not be modified or upgraded out of band, as doing so would most likely 14 | # jeopardize the stability of your CAS deployment leading to unpredictable behavior. 15 | springBootVersion=4.0.0 16 | 17 | # The coordinates of this overlay project 18 | group=org.apereo.cas 19 | artifactId=cas-overlay 20 | 21 | # Before changing the JDK versions here, you must account for the following: 22 | # - Dependency Compatibility: Ensure that all libraries and frameworks you use are compatible with Java 25 and above. 23 | # - Environment Compatibility: Check that your deployment environments (e.g., servers, CI/CD pipelines, cloud services) support Java 25 and above. 24 | # Remember that this CAS build does and will only officially support Java 25. Do NOT change platform requirements unless 25 | # you really know what you are doing and are comfortable managing the deployment and its risks completely on your own. 26 | 27 | # This property defines the version of Java that your source code is written in. 28 | # It ensures that your code is compatible with the specified version of the Java language. 29 | # Gradle will expect your source code to be compatible with JDK 25. 30 | sourceCompatibility=25 31 | 32 | # This property specifies the version of the Java runtime that the compiled bytecode should be compatible with. 33 | # It ensures that the bytecode generated by the compiler will run on JDK 25. 34 | targetCompatibility=25 35 | 36 | # This property controls the JVM vendor that is used by Gradle toolchains. 37 | # You may want to build CAS using a Java version that is not supported for running Gradle 38 | # by setting this property to the vendor of the JDK you want to use. 39 | # If Gradle can’t find a locally available toolchain that matches the requirements 40 | # of the build, it can automatically download one. 41 | # Options include: AMAZON, ADOPTIUM, JETBRAINS, MICROSOFT, ORACLE, SAP, BELLSOFT, etc. 42 | # Setting this to a blank or invalid value will force Gradle to use the JDK installation on the build machine. 43 | jvmVendor=AMAZON 44 | 45 | # This plugin controls how JDK distributions required by the Grtadle toolchain 46 | # are discovered, and downloaded when necessary. 47 | # Note that auto-provisioning of a JDK distribution only kicks in when auto-detection fails 48 | # to find a matching JDK, and auto-provisioning can only download new JDKs and is in no way 49 | # involved in updating any of the already installed ones. 50 | gradleFoojayPluginVersion=1.0.0 51 | 52 | gradleFreeFairPluginVersion=9.1.0 53 | lombokVersion=1.18.42 54 | 55 | # Used to build Docker images 56 | jibVersion=3.5.2 57 | gradleDockerPluginVersion=9.4.0 58 | 59 | # Specify the coordinates of the container image to build 60 | containerImageOrg=apereo 61 | containerImageName=cas 62 | 63 | baseDockerImage=azul/zulu-openjdk:25 64 | allowInsecureRegistries=false 65 | 66 | # Multiple platforms may be specified, separated by a comma i.e amd64:linux,arm64:linux 67 | dockerImagePlatform=amd64:linux 68 | 69 | 70 | # The plugin creates an aggregate of all dependencies of the CAS project 71 | # and creates a valid SBOM, designed for use in application security contexts 72 | # and supply chain component analysis. 73 | gradleCyclonePluginVersion=3.0.2 74 | 75 | # Use -tomcat, -jetty, -undertow for deployment to control the embedded server container 76 | # that will be used to deploy and manage your CAS deployment. 77 | # You should set this to blank if you want to deploy to an external container. 78 | # and want to set up, download and manage the container (i.e. Apache Tomcat) yourself. 79 | appServer=-tomcat 80 | 81 | # If you are using an embedded Apache Tomcat container to deploy and run CAS, 82 | # and need to override the Apache Tomcat version, uncomment the property below 83 | # and specify the the Apache Tomcat version, i.e. 11.0.15. 84 | # While enabled, this will override any and all upstream changes to 85 | # Apache Tomcat dependency management and you will be directly responsible to make 86 | # adjustments and upgrades as necessary. Use with caution, favor less work. 87 | # tomcatVersion=11.0.15 88 | 89 | # Settings to generate a keystore 90 | # used by the build to assist with creating 91 | # self-signed certificates for TLS 92 | certDir=/etc/cas 93 | serverKeystore=thekeystore 94 | exportedServerCert=cas.crt 95 | storeType=PKCS12 96 | 97 | # Location of the downloaded CAS Shell JAR 98 | shellDir=build/libs 99 | ivyVersion=2.5.2 100 | gradleDownloadTaskVersion=5.6.0 101 | 102 | # Include private repository 103 | # override these in user properties or pass in values from env on command line 104 | privateRepoUrl= 105 | privateRepoUsername= 106 | 107 | # Gradle build settings 108 | # Do NOT modify unless you know what you're doing! 109 | org.gradle.configureondemand=true 110 | org.gradle.caching=true 111 | org.gradle.parallel=true 112 | org.gradle.jvmargs=-Xms1024m -Xmx4048m -XX:TieredStopAtLevel=1 113 | org.gradle.unsafe.configuration-cache=false 114 | org.gradle.unsafe.configuration-cache-problems=warn 115 | systemProp.org.gradle.internal.http.connectionTimeout=60000 116 | systemProp.org.gradle.internal.http.requestTimeout=120000 117 | -------------------------------------------------------------------------------- /etc/cas/config/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | /var/log 10 | info 11 | warn 12 | info 13 | warn 14 | warn 15 | warn 16 | warn 17 | warn 18 | warn 19 | warn 20 | warn 21 | true 22 | false 23 | 24 | casStackTraceFile 25 | false 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /helm/cas-server/templates/statefulset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: {{ include "cas-server.fullname" . }} 5 | labels: {{- include "cas-server.labels" . | nindent 4 }} 6 | spec: 7 | replicas: {{ .Values.replicaCount }} 8 | updateStrategy: 9 | type: {{ .Values.updateStrategy | quote}} 10 | serviceName: {{ include "cas-server.fullname" . }} 11 | podManagementPolicy: {{ .Values.podManagementPolicy | quote}} 12 | selector: 13 | matchLabels: 14 | {{- include "cas-server.selectorLabels" . | nindent 6 }} 15 | template: 16 | metadata: 17 | annotations: 18 | {{- with .Values.podAnnotations }} 19 | {{- toYaml . | nindent 8 }} 20 | {{- end }} 21 | {{ if .Values.casServerContainer.alwaysRoll }} 22 | rollme: {{ randAlphaNum 5 | quote }} 23 | {{- else }} 24 | rollme: "rolldisabled" 25 | {{- end }} 26 | labels: 27 | {{- include "cas-server.selectorLabels" . | nindent 8 }} 28 | spec: 29 | {{- with .Values.imagePullSecrets }} 30 | imagePullSecrets: 31 | {{- toYaml . | nindent 8 }} 32 | {{- end }} 33 | serviceAccountName: {{ include "cas-server.serviceAccountName" . }} 34 | {{- if .Values.podSecurityContext.enabled }} 35 | securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }} 36 | {{- end }} 37 | volumes: 38 | {{- range $.Values.casServerContainer.casConfigMounts }} 39 | {{- $configMount := printf "%s-%s" "cas-config" . | replace "." "-" | replace "_" "-" | lower }} 40 | - name: {{ $configMount | quote }} 41 | configMap: 42 | name: {{ include "cas-server.fullname" $ }}-casconfig 43 | defaultMode: 0644 44 | {{- end }} 45 | - name: scripts 46 | configMap: 47 | name: {{ include "cas-server.fullname" . }}-scripts 48 | defaultMode: 0755 49 | - name: logdir 50 | {{- include "cas-server.logdir" . | nindent 10 }} 51 | {{- if .Values.casServerContainer.serverKeystoreExistingSecret }} 52 | - name: cas-server-keystore 53 | secret: 54 | secretName: {{ .Values.casServerContainer.serverKeystoreExistingSecret }} 55 | defaultMode: 0444 56 | items: 57 | - key: {{ .Values.casServerContainer.serverKeystoreSubPath }} 58 | path: {{ .Values.casServerContainer.serverKeystoreSubPath }} 59 | {{- end }} 60 | {{- if .Values.casServerContainer.extraVolumes }} 61 | {{- include "cas-server.tplvalues.render" ( dict "value" .Values.casServerContainer.extraVolumes "context" $ ) | nindent 8 }} 62 | {{- end }} 63 | {{- if or .Values.casServerContainer.initContainers (and .Values.podSecurityContext.enabled .Values.volumePermissions.enabled .Values.persistence.enabled) }} 64 | initContainers: 65 | {{- if and .Values.podSecurityContext.enabled .Values.volumePermissions.enabled .Values.persistence.enabled }} 66 | - name: volume-permissions 67 | image: {{ include "cas-server.volumePermissions.image" . }} 68 | imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} 69 | command: 70 | - /bin/sh 71 | - -cx 72 | - | 73 | {{- if .Values.persistence.enabled }} 74 | {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} 75 | chown `id -u`:`id -G | cut -d " " -f2` {{ .Values.persistence.mountPath }} 76 | {{- else }} 77 | chown {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }} {{ .Values.persistence.mountPath }} 78 | {{- end }} 79 | mkdir -p {{ .Values.persistence.mountPath }}/data 80 | chmod 700 {{ .Values.persistence.mountPath }}/data 81 | find {{ .Values.persistence.mountPath }} -mindepth 1 -maxdepth 1 -not -name ".snapshot" -not -name "lost+found" | \ 82 | {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} 83 | xargs chown -R `id -u`:`id -G | cut -d " " -f2` 84 | {{- else }} 85 | xargs chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }} 86 | {{- end }} 87 | {{- end }} 88 | {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} 89 | securityContext: {{- omit .Values.volumePermissions.securityContext "runAsUser" | toYaml | nindent 12 }} 90 | {{- else }} 91 | securityContext: {{- .Values.volumePermissions.securityContext | toYaml | nindent 12 }} 92 | {{- end }} 93 | {{- if .Values.volumePermissions.resources }} 94 | resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }} 95 | {{- end }} 96 | volumeMounts: 97 | - name: data 98 | mountPath: {{ .Values.persistence.mountPath }} 99 | {{- end }} 100 | {{- if .Values.casServerContainer.initContainers }} 101 | {{- include "cas-server.tplvalues.render" (dict "value" .Values.casServerContainer.initContainers "context" $) | nindent 8 }} 102 | {{- end }} 103 | {{- end }} 104 | containers: 105 | - name: {{ .Chart.Name }} 106 | {{- if .Values.containerSecurityContext.enabled }} 107 | securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} 108 | {{- end }} 109 | image: {{ include "cas-server.imageName" . }} 110 | imagePullPolicy: {{ .Values.image.pullPolicy }} 111 | env: 112 | {{- if .Values.casServerContainer.warPath }} 113 | - name: CAS_WAR 114 | value: {{ .Values.casServerContainer.warPath | quote }} 115 | {{- end }} 116 | {{- if .Values.casServerContainer.profiles }} 117 | - name: CAS_SPRING_PROFILES 118 | value: {{ .Values.casServerContainer.profiles | quote }} 119 | {{- end }} 120 | {{- if .Values.casServerContainer.jvm.maxHeapOpt }} 121 | - name: MAX_HEAP_OPT 122 | value: {{ .Values.casServerContainer.jvm.maxHeapOpt | quote }} 123 | {{- end }} 124 | {{- if .Values.casServerContainer.jvm.minHeapOpt }} 125 | - name: MIN_HEAP_OPT 126 | value: {{ .Values.casServerContainer.jvm.minHeapOpt | quote }} 127 | {{- end }} 128 | {{- if .Values.casServerContainer.jvm.extraOpts }} 129 | - name: JVM_EXTRA_OPTS 130 | value: {{ .Values.casServerContainer.jvm.extraOpts | quote }} 131 | {{- end }} 132 | - name: JAVA_ENABLE_DEBUG 133 | value: {{ .Values.casServerContainer.jvm.debugEnabled | quote }} 134 | - name: JAVA_DEBUG_SUSPEND 135 | value: {{ .Values.casServerContainer.jvm.debugSuspend | quote }} 136 | - name: 'KUBERNETES_NAMESPACE' # used by org.apache.catalina.tribes.membership.cloud.CloudMembershipProvider 137 | value: {{ .Release.Namespace }} 138 | - name: 'POD_IP' 139 | valueFrom: 140 | fieldRef: 141 | fieldPath: status.podIP 142 | {{- if .Values.casServerContainer.extraEnvVars }} 143 | {{- include "cas-server.tplvalues.render" (dict "value" .Values.casServerContainer.extraEnvVars "context" $) | nindent 12 }} 144 | {{- end }} 145 | envFrom: 146 | {{- if .Values.casServerContainer.extraEnvVarsConfigMap }} 147 | - configMapRef: 148 | name: {{ .Values.casServerContainer.extraEnvVarsConfigMap }} 149 | {{- end }} 150 | {{- if .Values.casServerContainer.extraEnvVarsSecret }} 151 | - secretRef: 152 | name: {{ .Values.casServerContainer.extraEnvVarsSecret }} 153 | {{- end }} 154 | {{- if .Values.casServerContainer.command }} 155 | command: {{- include "cas-server.tplvalues.render" (dict "value" .Values.casServerContainer.command "context" $) | nindent 12 }} 156 | {{- else }} 157 | command: 158 | - '/entrypoint.sh' 159 | {{- end }} 160 | {{- if .Values.casServerContainer.args }} 161 | args: {{- include "cas-server.tplvalues.render" (dict "value" .Values.casServerContainer.args "context" $) | nindent 12 }} 162 | {{- end }} 163 | ports: 164 | - name: https 165 | containerPort: {{ .Values.cas.listenPortHttps }} 166 | protocol: TCP 167 | - name: jvm-debug 168 | containerPort: {{ .Values.cas.listenPortJvmDebug }} 169 | protocol: TCP 170 | volumeMounts: 171 | {{- if .Values.persistence.enabled }} 172 | - name: data 173 | mountPath: {{ .Values.persistence.mountPath }} 174 | {{- end }} 175 | {{- range $.Values.casServerContainer.casConfigMounts }} 176 | {{- $configMount := printf "%s-%s" "cas-config" . | replace "." "-" | replace "_" "-" | lower }} 177 | {{- $configMountPath := printf "%s/%s" "/etc/cas/config" . }} 178 | - name: {{ $configMount | quote }} 179 | mountPath: {{ $configMountPath }} 180 | subPath: {{ . | quote }} 181 | {{- end }} 182 | - name: scripts 183 | mountPath: /entrypoint.sh 184 | subPath: entrypoint.sh 185 | - name: logdir 186 | mountPath: {{ .Values.logdir.mountPath }} 187 | {{- if .Values.casServerContainer.serverKeystoreExistingSecret }} 188 | - name: cas-server-keystore 189 | mountPath: {{ .Values.casServerContainer.serverKeystoreMountPath }} 190 | subPath: {{ .Values.casServerContainer.serverKeystoreSubPath }} 191 | {{- end }} 192 | {{- if .Values.casServerContainer.extraVolumeMounts }} 193 | {{- include "cas-server.tplvalues.render" ( dict "value" .Values.casServerContainer.extraVolumeMounts "context" $ ) | nindent 12 }} 194 | {{- end }} 195 | startupProbe: 196 | httpGet: 197 | path: {{ .Values.casServerContainer.defaultStatusUrl }} 198 | port: https 199 | scheme: HTTPS 200 | {{- if .Values.casServerContainer.defaultStatusHeaders }} 201 | {{- include "cas-server.tplvalues.render" ( dict "value" .Values.casServerContainer.defaultStatusHeaders "context" $ ) | nindent 14 }} 202 | {{- end }} 203 | failureThreshold: {{ .Values.casServerContainer.startupFailureThreshold }} 204 | periodSeconds: 20 205 | readinessProbe: 206 | httpGet: 207 | path: {{ .Values.casServerContainer.defaultStatusUrl }} 208 | port: https 209 | scheme: HTTPS 210 | {{- if .Values.casServerContainer.defaultStatusHeaders }} 211 | {{- include "cas-server.tplvalues.render" ( dict "value" .Values.casServerContainer.defaultStatusHeaders "context" $ ) | nindent 14 }} 212 | {{- end }} 213 | initialDelaySeconds: {{ .Values.casServerContainer.readinessInitialDelaySeconds }} 214 | periodSeconds: 5 215 | failureThreshold: {{ .Values.casServerContainer.readinessFailureThreshold }} 216 | livenessProbe: 217 | httpGet: 218 | path: {{ .Values.casServerContainer.defaultStatusUrl }} 219 | port: https 220 | scheme: HTTPS 221 | {{- if .Values.casServerContainer.defaultStatusHeaders }} 222 | {{- include "cas-server.tplvalues.render" ( dict "value" .Values.casServerContainer.defaultStatusHeaders "context" $ ) | nindent 14 }} 223 | {{- end }} 224 | initialDelaySeconds: {{ .Values.casServerContainer.livenessInitialDelaySeconds }} 225 | periodSeconds: 15 226 | failureThreshold: {{ .Values.casServerContainer.livenessFailureThreshold }} 227 | resources: 228 | {{- toYaml .Values.resources | nindent 12 }} 229 | {{- with .Values.nodeSelector }} 230 | nodeSelector: 231 | {{- toYaml . | nindent 8 }} 232 | {{- end }} 233 | {{- with .Values.affinity }} 234 | affinity: 235 | {{- toYaml . | nindent 8 }} 236 | {{- end }} 237 | {{- with .Values.tolerations }} 238 | tolerations: 239 | {{- toYaml . | nindent 8 }} 240 | {{- end }} 241 | {{- if .Values.persistence.enabled }} 242 | volumeClaimTemplates: 243 | - metadata: 244 | name: data 245 | {{- with .Values.persistence.annotations }} 246 | annotations: 247 | {{- range $key, $value := . }} 248 | {{ $key }}: {{ $value }} 249 | {{- end }} 250 | {{- end }} 251 | spec: 252 | accessModes: 253 | {{- range .Values.persistence.accessModes }} 254 | - {{ . | quote }} 255 | {{- end }} 256 | resources: 257 | requests: 258 | storage: {{ .Values.persistence.size | quote }} 259 | storageClassName: {{ .Values.persistence.storageClassName | quote }} 260 | {{- if .Values.persistence.selector }} 261 | selector: {{- include "cas-server.tplvalues.render" (dict "value" .Values.persistence.selector "context" $) | nindent 10 }} 262 | {{- end -}} 263 | {{- end }} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IMPORTANT NOTE
******************************************************
This repository is always automatically generated from the [CAS Initializr](https://github.com/apereo/cas-initializr). Do NOT submit pull requests here as the change-set will be overwritten on the next sync. To learn more, please visit the [CAS documentation](https://apereo.github.io/cas).
******************************************************
2 | Apereo CAS WAR Overlay Template 3 | ===================================== 4 | 5 | WAR Overlay Type: `cas-overlay` 6 | 7 | # Versions 8 | 9 | - CAS Server `8.0.0-SNAPSHOT` 10 | - JDK `25` 11 | 12 | # Build 13 | 14 | To build the project, use: 15 | 16 | ```bash 17 | # Use --refresh-dependencies to force-update SNAPSHOT versions 18 | ./gradlew[.bat] clean build 19 | ``` 20 | 21 | To see what commands/tasks are available to the build script, run: 22 | 23 | ```bash 24 | ./gradlew[.bat] tasks 25 | ``` 26 | 27 | If you need to, on Linux/Unix systems, you can delete all the existing artifacts 28 | (artifacts and metadata) Gradle has downloaded using: 29 | 30 | ```bash 31 | # Only do this when absolutely necessary 32 | rm -rf $HOME/.gradle/caches/ 33 | ``` 34 | 35 | Same strategy applies to Windows too, provided you switch `$HOME` to its equivalent in the above command. 36 | 37 | # Keystore 38 | 39 | For the server to run successfully, you might need to create a keystore file. 40 | This can either be done using the JDK's `keytool` utility or via the following command: 41 | 42 | ```bash 43 | ./gradlew[.bat] createKeystore 44 | ``` 45 | 46 | Use the password `changeit` for both the keystore and the key/certificate entries. 47 | Ensure the keystore is loaded up with keys and certificates of the server. 48 | 49 | # Extension Modules 50 | 51 | Extension modules may be specified under the `dependencies` block of the [Gradle build script](build.gradle): 52 | 53 | ```gradle 54 | dependencies { 55 | implementation "org.apereo.cas:cas-server-some-module" 56 | ... 57 | } 58 | ``` 59 | 60 | To collect the list of all project modules and dependencies in the overlay: 61 | 62 | ```bash 63 | ./gradlew[.bat] dependencies 64 | ``` 65 | 66 | # Deployment 67 | 68 | On a successful deployment via the following methods, the server will be available at: 69 | 70 | * `https://localhost:8443/cas` 71 | 72 | 73 | ## Executable WAR 74 | 75 | Run the server web application as an executable WAR. Note that running an executable WAR requires CAS to use an embedded container such as Apache Tomcat, Jetty, etc. 76 | 77 | The current servlet container is specified as `-tomcat`. 78 | 79 | ```bash 80 | java -jar build/libs/cas.war 81 | ``` 82 | 83 | Or via: 84 | 85 | ```bash 86 | ./gradlew[.bat] run 87 | ``` 88 | 89 | It is often an advantage to explode the generated web application and run it in unpacked mode. 90 | One way to run an unpacked archive is by starting the appropriate launcher, as follows: 91 | 92 | ```bash 93 | jar -xf build/libs/cas.war 94 | cd build/libs 95 | java org.springframework.boot.loader.launch.JarLauncher 96 | ``` 97 | 98 | This is slightly faster on startup (depending on the size of the WAR file) than 99 | running from an unexploded archive. After startup, you should not expect any differences. 100 | 101 | Debug the CAS web application as an executable WAR: 102 | 103 | ```bash 104 | ./gradlew[.bat] debug 105 | ``` 106 | 107 | Or via: 108 | 109 | ```bash 110 | java -Xdebug -Xrunjdwp:transport=dt_socket,address=5000,server=y,suspend=y -jar build/libs/cas.war 111 | ``` 112 | 113 | 114 | ### CDS Support 115 | 116 | CDS is a JVM feature that can help reduce the startup time and memory footprint of Java applications. CAS via Spring Boot 117 | now has support for easy creation of a CDS friendly layout. This layout can be created by extracting the CAS web application file 118 | with the help of the `tools` jarmode: 119 | 120 | ```bash 121 | 122 | java -Djarmode=tools -jar build/libs/cas.war extract 123 | 124 | # Perform a training run once 125 | java -XX:ArchiveClassesAtExit=cas.jsa -Dspring.context.exit=onRefresh -jar cas/cas.war 126 | 127 | # Run the CAS web application via CDS 128 | java XX:SharedArchiveFile=cas.jsa -jar cas/cas.war 129 | ``` 130 | 131 | ## External 132 | 133 | Deploy the binary web application file in `build/libs` after a successful build to a servlet container of choice. 134 | 135 | # Docker 136 | 137 | The following strategies outline how to build and deploy CAS Docker images. 138 | 139 | ## Jib 140 | 141 | The overlay embraces the [Jib Gradle Plugin](https://github.com/GoogleContainerTools/jib) to provide easy-to-use out-of-the-box tooling for building CAS docker images. Jib is an open-source Java containerizer from Google that lets Java developers build containers using the tools they know. It is a container image builder that handles all the steps of packaging your application into a container image. It does not require you to write a Dockerfile or have Docker installed, and it is directly integrated into the overlay. 142 | 143 | ```bash 144 | # Running this task requires that you have Docker installed and running. 145 | ./gradlew build jibDockerBuild 146 | ``` 147 | 148 | ## Dockerfile 149 | 150 | You can also use the Docker tooling and the provided `Dockerfile` to build and run. 151 | There are dedicated Gradle tasks available to build and push Docker images using the supplied `DockerFile`: 152 | 153 | ```bash 154 | ./gradlew build casBuildDockerImage 155 | ``` 156 | 157 | Once ready, you may also push the images: 158 | 159 | ```bash 160 | ./gradlew casPushDockerImage 161 | ``` 162 | 163 | If credentials (username+password) are required for pull and push operations, they may be specified 164 | using system properties via `-DdockerUsername=...` and `-DdockerPassword=...`. 165 | 166 | A `docker-compose.yml` is also provided to orchestrate the build: 167 | 168 | ```bash 169 | docker-compose build 170 | ``` 171 | 172 | ## Spring Boot 173 | 174 | You can use the Spring Boot build plugin for Gradle to create CAS container images. 175 | The plugins create an OCI image (the same format as one created by docker build) 176 | by using [Cloud Native Buildpacks](https://buildpacks.io/). You do not need a Dockerfile, but you do need a Docker daemon, 177 | either locally (which is what you use when you build with docker) or remotely 178 | through the `DOCKER_HOST` environment variable. The default builder is optimized for 179 | Spring Boot applications such as CAS, and the image is layered efficiently. 180 | 181 | ```bash 182 | ./gradlew bootBuildImage 183 | ``` 184 | 185 | The first build might take a long time because it has to download some container 186 | images and the JDK, but subsequent builds should be fast. 187 | 188 | 189 | # Software Bill of Materials (SBOM) 190 | 191 | The build supports generating a Software Bill of Materials (SBOM) for the CAS web application. This is done using 192 | the Gradle CycloneDX plugin, which creates an aggregate of all direct and transitive dependencies of a project 193 | and creates a valid CycloneDX SBOM. CycloneDX is a lightweight software bill of materials (SBOM) 194 | specification designed for use in application security contexts and supply chain component analysis. 195 | 196 | You may run the following Gradle task to generate the SBOM: 197 | 198 | ```bash 199 | ./gradlew cyclonedxBom 200 | ``` 201 | 202 | The generated SBOM file will be located in the `build/META-INF/sbom/application.cdx` file. 203 | # CAS Command-line Shell 204 | 205 | To launch into the CAS command-line shell: 206 | 207 | ```bash 208 | ./gradlew[.bat] downloadShell runShell 209 | ``` 210 | 211 | # Retrieve Overlay Resources 212 | 213 | To fetch and overlay a CAS resource or view, use: 214 | 215 | ```bash 216 | ./gradlew[.bat] getResource -PresourceName=[resource-name] 217 | ``` 218 | 219 | # Create User Interface Themes Structure 220 | 221 | You can use the overlay to construct the correct directory structure for custom user interface themes: 222 | 223 | ```bash 224 | ./gradlew[.bat] createTheme -Ptheme=redbeard 225 | ``` 226 | 227 | The generated directory structure should match the following: 228 | 229 | ``` 230 | ├── redbeard.properties 231 | ├── static 232 | │ └── themes 233 | │ └── redbeard 234 | │ ├── css 235 | │ │ └── cas.css 236 | │ └── js 237 | │ └── cas.js 238 | └── templates 239 | └── redbeard 240 | └── fragments 241 | ``` 242 | 243 | HTML templates and fragments can be moved into the above directory structure, 244 | and the theme may be assigned to applications for use. 245 | 246 | # List Overlay Resources 247 | 248 | To list all available CAS views and templates: 249 | 250 | ```bash 251 | ./gradlew[.bat] listTemplateViews 252 | ``` 253 | 254 | To unzip and explode the CAS web application file and the internal resources jar: 255 | 256 | ```bash 257 | ./gradlew[.bat] explodeWar 258 | ``` 259 | 260 | # Configuration 261 | 262 | - The `etc` directory contains the configuration files and directories that need to be copied to `/etc/cas/config`. 263 | 264 | ```bash 265 | ./gradlew[.bat] copyCasConfiguration 266 | ``` 267 | 268 | - The specifics of the build are controlled using the `gradle.properties` file. 269 | 270 | ## Configuration Metadata 271 | 272 | Configuration metadata allows you to export collection of CAS properties as a report into a file 273 | that can later be examined. You will find a full list of CAS settings along with notes, types, default and accepted values: 274 | 275 | ```bash 276 | ./gradlew exportConfigMetadata 277 | ``` 278 | 279 | # Puppeteer 280 | 281 | > [Puppeteer](https://pptr.dev/) is a Node.js library which provides a high-level API to control Chrome/Chromium over the DevTools Protocol. 282 | > Puppeteer runs in headless mode by default, but can be configured to run in full (non-headless) Chrome/Chromium. 283 | 284 | Puppeteer scenarios, used here as a form of acceptance testing, allow you to verify CAS functionality to address a particular authentication flow. The scenarios, which may be 285 | found inside the `./puppeteer/scenarios` directory are designed as small Node.js scripts that spin up a headless browser and walk through a test scenario. You may 286 | design your own test scenarios that verify functionality specific to your CAS deployment or feature. 287 | 288 | To execute Puppeteer scenarios, run: 289 | 290 | ```bash 291 | ./puppeteer/run.sh 292 | ``` 293 | 294 | This will first attempt to build your CAS deployment, will install Puppeteer and all other needed libraries. It will then launch the CAS server, 295 | and upon its availability, will iterate through defined scenarios and will execute them one at a time. 296 | 297 | The following defaults are assumed: 298 | 299 | - CAS will be available at `https://localhost:8443/cas/login`. 300 | - The CAS overlay is prepped with an embedded server container, such as Apache Tomcat. 301 | 302 | You may of course need to make adjustments to account for your specific environment and deployment settings, URLs, etc. 303 | 304 | 305 | # Duct 306 | 307 | `duct` is a Gradle task to do quick smoke tests of multi-node CAS high-availability deployments. In particular, it tests correctness of ticket 308 | sharing between multiple individual CAS server nodes backed by distributed ticket registries such as Hazelcast, Redis, etc. 309 | 310 | This task requires CAS server nodes to **enable the CAS REST module**. It will **NOT** work without it. 311 | 312 | The task accepts the following properties: 313 | 314 | - Arbitrary number of CAS server nodes specified via the `duct.cas.X` properties. 315 | - URL of the service application registered with CAS specified via `duct.service`, for which tickets will be requested. 316 | - `duct.username` and `duct.password` to use for authentication, when requesting ticket-granting tickets. 317 | 318 | It automates the following scenario: 319 | 320 | - Authenticate and issue a service ticket on one CAS node 321 | - Validate this service ticket on the another node 322 | - Repeat (You may cancel and stop the task at any time with `Ctrl+C`) 323 | 324 | If the task succeeds, then we effectively have proven that the distributed ticket registry has been set up and deployed 325 | correctly and that there are no connectivity issues between CAS nodes. 326 | 327 | To run the task, you may use: 328 | 329 | ```bash 330 | ./gradlew duct 331 | -Pduct.cas.1=https://node1.example.org/cas \ 332 | -Pduct.cas.2=https://node2.example.org/cas \ 333 | -Pduct.cas.3=https://node3.example.org/cas \ 334 | -Pduct.cas.4=https://node4.example.org/cas \ 335 | -Pduct.service=https://apereo.github.io \ 336 | -Pduct.username=casuser \ 337 | -Pduct.password=Mellon 338 | ``` 339 | 340 | You may also supply the following options: 341 | 342 | - `duct.debug`: Boolean flag to output debug and verbose logging. 343 | - `duct.duration`: Number of seconds, i.e. `30` to execute the scenario. 344 | - `duct.count`: Number of iterations, i.e. `5` to execute the scenario. 345 | 346 | 347 | # OpenRewrite 348 | 349 | [OpenRewrite](https://docs.openrewrite.org/) is a tool used by the CAS in form of a Gradle plugin 350 | that allows the project to upgrade in place. It works by making changes to the project structure representing 351 | your CAS build and printing the modified files back. Modifications are packaged together in form of upgrade 352 | scripts called `Recipes` that are automatically packaged and presented to the build and may be discovered via: 353 | 354 | ```bash 355 | ./gradlew --init-script openrewrite.gradle rewriteDiscover -PtargetVersion=X.Y.Z --no-configuration-cache | grep "org.apereo.cas" 356 | ``` 357 | 358 | **NOTE:** All CAS specific recipes begin with `org.apereo.cas`. The `targetVersion` must be the CAS version to which you want to upgrade. 359 | 360 | OpenRewrite recipes make minimally invasive changes to your CAS build allowing you to upgrade from one version 361 | to the next with minimal effort. The recipe contains *almost* everything that is required for a CAS build system to navigate 362 | from one version to other and automated tedious aspects of the upgrade such as finding the correct versions of CAS, 363 | relevant libraries and plugins as well as any possible structural changes to one's CAS build. 364 | 365 | To run, you will need to find and select the name of the recipe first. Then, you can dry-run the selected recipes and see which files would be changed in the build log. 366 | This does not alter your source files on disk at all. This goal can be used to preview the changes that would be made by the active recipes. 367 | 368 | ```bash 369 | ./gradlew --init-script openrewrite.gradle rewriteDryRun -PtargetVersion=X.Y.Z -DactiveRecipe=[recipe name] --no-configuration-cache 370 | ``` 371 | 372 | When you are ready, you can run the actual recipe: 373 | 374 | ```bash 375 | ./gradlew --init-script openrewrite.gradle rewriteRun -PtargetVersion=X.Y.Z -DactiveRecipe=[recipe name] --no-configuration-cache 376 | ``` 377 | 378 | This will run the selected recipes and apply the changes. This will write changes locally to your source files on disk. 379 | Afterward, review the changes, and when you are comfortable with the changes, commit them. 380 | The run goal generates warnings in the build log wherever it makes changes to source files. 381 | -------------------------------------------------------------------------------- /helm/cas-server/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for cas-server. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | casServerName: cas.example.org 6 | 7 | replicaCount: 1 8 | 9 | image: 10 | registry: "" 11 | repository: "apereo/cas" 12 | pullPolicy: IfNotPresent 13 | # Overrides the image tag whose default is the chart appVersion. 14 | tag: "latest" 15 | 16 | 17 | imagePullSecrets: [] 18 | nameOverride: "" 19 | fullnameOverride: "" 20 | 21 | # There are two valid stateful set update strategies, RollingUpdate and the (legacy) OnDelete 22 | updateStrategy: RollingUpdate 23 | 24 | # OrderedReady: Pods are created in increasing order (pod-0, then pod-1, etc) and the controller will wait until each pod is ready before continuing. 25 | # When scaling down, the pods are removed in the opposite order. 26 | # Parallel: Creates pods in parallel to match the desired scale without waiting, and on scale down will delete all pods at once. 27 | podManagementPolicy: OrderedReady 28 | 29 | # Map folder for logs directory from host or pvc, or leave both blank to use emptyDir volume 30 | # In docker for windows hostPath could be '/host_mnt/c/opt/cas/logs' 31 | # Windows: Give full access local Users group to the to ~/.docker folder if getting permission denied) 32 | logdir: 33 | # hostPath: '/host_mnt/c/opt/cas/logs' 34 | hostPath: '' 35 | claimName: '' 36 | mountPath: '/var/log' 37 | 38 | # CAS Server container properties 39 | casServerContainer: 40 | ## Roll on upgrade changes deployment when helm upgrade runs, forcing pod to restart 41 | alwaysRoll: false 42 | ## JVM Settings 43 | ## JVM settings only used if command not set, use args to set app arguments 44 | jvm: 45 | ## Extra JVM options 46 | ## 47 | extraOpts: '-Djavax.net.ssl.trustStore=/etc/cas/truststore -Djavax.net.ssl.trustStoreType=PKCS12 -Djavax.net.ssl.trustStorePassword=changeit' 48 | 49 | ## Memory settings: If these aren't defined, java will calc values automatically, but requires setting limits on pod 50 | ## so it doesn't base heap size on host memory 51 | maxHeapOpt: '-Xmx2G' 52 | newHeapOpt: '-Xms600M' 53 | debugEnabled: true 54 | debugSuspend: "n" # could be n or y, must quote or yaml changes to boolean 55 | warPath: 'cas.war' 56 | ## Override cmd 57 | ## 58 | command: 59 | ## Override args 60 | ## 61 | args: 62 | ## extraVolumes and extraVolumeMounts allows you to mount other volumes 63 | ## Examples: 64 | ## extraVolumeMounts: 65 | ## - name: extras 66 | ## mountPath: /usr/share/extras 67 | ## readOnly: true 68 | ## extraVolumes: 69 | ## - name: extras 70 | ## emptyDir: {} 71 | ## 72 | profiles: 'standalone' 73 | 74 | extraVolumeMounts: 75 | - name: truststore 76 | mountPath: /etc/cas/truststore 77 | subPath: truststore 78 | 79 | extraVolumes: 80 | - name: truststore 81 | configMap: 82 | name: cas-truststore 83 | defaultMode: 0444 84 | 85 | ## Url to use for readiness, startupprobe, and liveliness check, change to health actuator if the module is available 86 | ## Naming it "default" in case in future template supports individual urls for the different checks, with this as default if they aren't specified 87 | defaultStatusUrl: '/cas/actuator/health' 88 | 89 | # number of startup probe failures before it will be killed, set high if trying to debug startup issues 90 | # liveness and readiness failure threshold might be 1 but startup failure threshold accounts for 91 | # failures while server is starting up 92 | startupFailureThreshold: 30 93 | livenessFailureThreshold: 1 94 | readinessFailureThreshold: 1 95 | readinessInitialDelaySeconds: 45 96 | livenessInitialDelaySeconds: 120 97 | 98 | ## Extra init containers to add to the statefulset 99 | ## 100 | initContainers: [] 101 | 102 | ## An array to add extra env vars 103 | ## For example: 104 | ## extraEnvVars: 105 | ## - name: MY_ENV_VAR 106 | ## value: env_var_value 107 | ## 108 | extraEnvVars: [] 109 | 110 | ## Name of a ConfigMap containing extra env vars 111 | ## 112 | extraEnvVarsConfigMap: '' 113 | 114 | # name of secret containing server keystore 115 | serverKeystoreExistingSecret: cas-server-keystore 116 | # folder that should container the keystore 117 | serverKeystoreMountPath: '/etc/cas/thekeystore' 118 | # name of keystore file in container and in secret 119 | serverKeystoreSubPath: 'thekeystore' 120 | 121 | ## Name of a Secret containing extra env vars 122 | ## 123 | extraEnvVarsSecret: '' 124 | ## Choose which config files from casConfig to mount 125 | casConfigMounts: 126 | - 'cas.properties' 127 | - 'cas.yaml' 128 | ## Create various config files from casConfig that may or may not be mounted 129 | casConfig: 130 | # issue with line breaks? means can't use {{}} variables after first line 131 | # workaround is to use {{}} variables in yaml version of properties file 132 | cas.properties: |- 133 | cas.server.name=https://{{ .Values.casServerName }} 134 | context.path=/cas 135 | cas.server.prefix=${cas.server.name}${context.path} 136 | 137 | cas.http-client.truststore.psw=changeit 138 | cas.http-client.truststore.file=/etc/cas/truststore 139 | 140 | # put web access logs in same directory as cas logs 141 | cas.server.tomcat.ext-access-log.directory=/var/log 142 | cas.server.tomcat.ext-access-log.enabled=true 143 | 144 | # uncomment the folowing to not allow login of built-in users 145 | # cas.authn.accept.users= 146 | 147 | # since we are behind ingress controller, need to use x-forwarded-for to get client ip 148 | # if nginx ingress controller is behind another proxy, it needs to be configured globally with the following settings in the ingress controller configmap 149 | # use-forwarded-headers: "true" # very important for CAS or any app that compares IP being used against IP that initiated sessions (session fixation) 150 | # enable-underscores-in-headers: "true" # while you are at it, allow underscores in headers, can't recall if important for cas but no need to have nginx dropping your headers with underscores 151 | cas.audit.engine.alternate-client-addr-header-name=X-Forwarded-For 152 | server.tomcat.remoteip.remote-ip-header=X-FORWARDED-FOR 153 | 154 | server.ssl.key-store=file:/etc/cas/thekeystore 155 | server.ssl.key-store-type=PKCS12 156 | server.ssl.key-store-password=changeit 157 | server.ssl.trust-store=file:/etc/cas/truststore 158 | server.ssl.trust-store-type=PKCS12 159 | server.ssl.trust-store-password=changeit 160 | 161 | # expose endpoints via http 162 | management.endpoints.web.exposure.include=health,info,prometheus,metrics,env,loggers,statistics,status,loggingConfig,events,configurationMetadata,caches 163 | management.endpoints.web.base-path=/actuator 164 | management.endpoints.web.cors.allowed-origins=https://${cas-host} 165 | management.endpoints.web.cors.allowed-methods=GET,POST 166 | 167 | # enable endpoints 168 | management.endpoint.metrics.enabled=true 169 | management.endpoint.health.enabled=true 170 | management.endpoint.info.enabled=true 171 | management.endpoint.env.enabled=true 172 | management.endpoint.loggers.enabled=true 173 | management.endpoint.status.enabled=true 174 | management.endpoint.statistics.enabled=true 175 | management.endpoint.prometheus.enabled=true 176 | management.endpoint.events.enabled=true 177 | management.endpoint.loggingConfig.enabled=true 178 | management.endpoint.configurationMetadata.enabled=true 179 | # configure health endpoint 180 | management.health.defaults.enabled=false 181 | management.health.ping.enabled=true 182 | management.health.caches.enabled=true 183 | 184 | # secure endpoints to localhost 185 | 186 | cas.monitor.endpoints.endpoint.defaults.access[0]=AUTHENTICATED 187 | cas.monitor.endpoints.endpoint.health.access[0]=IP_ADDRESS 188 | cas.monitor.endpoints.endpoint.health.requiredIpAddresses[0]=127.0.0.1 189 | cas.monitor.endpoints.endpoint.health.requiredIpAddresses[1]=0:0:0:0:0:0:0:1 190 | cas.monitor.endpoints.endpoint.health.requiredIpAddresses[2]=10\\..* 191 | cas.monitor.endpoints.endpoint.health.requiredIpAddresses[3]=172\\.16\\..* 192 | cas.monitor.endpoints.endpoint.health.requiredIpAddresses[4]=192\\.168\\..* 193 | #eof 194 | 195 | cas.yaml: |- 196 | --- 197 | logging: 198 | config: 'file:/etc/cas/config/log4j2.xml' 199 | cas: 200 | server: 201 | tomcat: 202 | clustering: 203 | enabled: true 204 | clustering-type: 'CLOUD' 205 | cloud-membership-provider: 'kubernetes' 206 | spring: 207 | security: 208 | user: 209 | name: "{{ .Values.casAdminUser }}" 210 | password: "{{ .Values.casAdminPassword }}" 211 | #eof 212 | 213 | podAnnotations: {} 214 | 215 | ## Pod security context 216 | ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod 217 | ## 218 | podSecurityContext: 219 | enabled: true 220 | fsGroup: 1000 221 | 222 | containerSecurityContext: 223 | enabled: false 224 | # capabilities: 225 | # drop: 226 | # - ALL 227 | # readOnlyRootFilesystem: true 228 | # runAsNonRoot: true 229 | runAsUser: 1000 230 | 231 | ## Override parts of this ingress in your own values file with appropriate host names 232 | ## This currently is only set up to work with Nginx Ingress Controller from Kubernetes project 233 | cas: 234 | service: 235 | type: ClusterIP 236 | publishNotReadyAddresses: true 237 | port: 8443 238 | listenPortHttps: 8443 239 | listenPortJvmDebug: 5005 240 | ingress: 241 | enabled: true 242 | annotations: 243 | kubernetes.io/ingress.class: nginx 244 | nginx.ingress.kubernetes.io/session-cookie-samesite: "None" 245 | nginx.ingress.kubernetes.io/session-cookie-conditional-samesite-none: "true" 246 | nginx.ingress.kubernetes.io/affinity: "cookie" 247 | nginx.ingress.kubernetes.io/session-cookie-name: "sticky-session-route" 248 | nginx.ingress.kubernetes.io/session-cookie-hash: "sha1" 249 | nginx.ingress.kubernetes.io/secure-backends: "true" 250 | nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" 251 | hosts: 252 | - host: cas.example.org 253 | paths: 254 | - "/cas" 255 | - host: kubernetes.docker.internal 256 | paths: 257 | - "/cas" 258 | tls: 259 | - secretName: cas-server-ingress-tls 260 | hosts: 261 | - cas.example.org 262 | - kubernetes.docker.internal 263 | 264 | # Request some resources for main cas server so kubernetes will schedule somewhere with enough resources 265 | # Limits can also be set if desired 266 | resources: 267 | requests: 268 | cpu: 100m 269 | memory: 512Mi 270 | # limits: 271 | # cpu: 100m 272 | # memory: 128Mi 273 | 274 | # node selector for CAS server 275 | nodeSelector: {} 276 | # tolerations for CAS server (i.e taints on nodes that it can tolerate) 277 | tolerations: [] 278 | # affinity config for CAS server 279 | affinity: {} 280 | 281 | casAdminUser: 'casuser' 282 | casAdminPassword: 'Mellon' 283 | 284 | # rbac may or may not be necessary, but it can allow for certain types of discovery (e.g. tomcat cloud session replication) 285 | rbac: 286 | # specified whether RBAC resources should be created 287 | create: true 288 | 289 | serviceAccount: 290 | # Specifies whether a service account should be created 291 | create: true 292 | # Annotations to add to the service account 293 | annotations: {} 294 | # The name of the service account to use. 295 | # If not set and create is true, a name is generated using the fullname template 296 | name: "" 297 | 298 | 299 | ## CAS can use a persistent volume to store config such as services and saml IDP/SP metadata that it pulls from git 300 | ## Enable persistence using Persistent Volume Claims 301 | ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ 302 | ## 303 | persistence: 304 | ## If true, use a Persistent Volume Claim for data folder mounted where you specify using mountPath 305 | ## 306 | enabled: true 307 | ## Persistent Volume Storage Class 308 | ## If defined, storageClassName: 309 | ## If set to "-", storageClassName: "", which disables dynamic provisioning 310 | ## If undefined (the default) or set to null, no storageClassName spec is 311 | ## set, choosing the default provisioner. (gp2 on AWS, standard on 312 | ## GKE, AWS & OpenStack) 313 | ## 314 | # storageClass: "-" 315 | ## Persistent Volume Claim annotations 316 | ## 317 | annotations: 318 | ## Persistent Volume Access Mode 319 | ## 320 | accessModes: 321 | - ReadWriteOnce 322 | ## Persistent Volume size 323 | ## 324 | size: 2Gi 325 | ## The path the volume will be mounted at, will contain writable folder called "data" under mountPath, 326 | ## if volumePermissions init container creates it 327 | ## 328 | mountPath: /var/cas 329 | 330 | ## Init containers parameters: 331 | ## volumePermissions: Change the owner and group of the persistent volume mountpoint to runAsUser:fsGroup values from 332 | ## the securityContext section. 333 | ## 334 | volumePermissions: 335 | enabled: false 336 | image: 337 | registry: docker.io 338 | repository: alpine 339 | tag: latest 340 | pullPolicy: Always 341 | ## Optionally specify an array of imagePullSecrets. 342 | ## Secrets must be manually created in the namespace. 343 | ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ 344 | ## 345 | # pullSecrets: 346 | # - myRegistryKeySecretName 347 | ## Init container' resource requests and limits 348 | ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ 349 | ## 350 | resources: 351 | # We usually recommend not to specify default resources and to leave this as a conscious 352 | # choice for the user. This also increases chances charts run on environments with little 353 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 354 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 355 | limits: {} 356 | # cpu: 100m 357 | # memory: 128Mi 358 | requests: {} 359 | # cpu: 100m 360 | # memory: 128Mi 361 | ## Init container Security Context 362 | ## Note: the chown of the data folder is done to securityContext.runAsUser 363 | ## and not the below volumePermissions.securityContext.runAsUser 364 | ## When runAsUser is set to special value "auto", init container will try to chown the 365 | ## data folder to autodetermined user&group, using commands: `id -u`:`id -G | cut -d" " -f2` 366 | ## "auto" is especially useful for OpenShift which has scc with dynamic userids (and 0 is not allowed). 367 | ## You may want to use this volumePermissions.securityContext.runAsUser="auto" in combination with 368 | ## pod securityContext.enabled=false and shmVolume.chmod.enabled=false 369 | ## 370 | securityContext: 371 | runAsUser: 0 372 | -------------------------------------------------------------------------------- /gradle/tasks.gradle: -------------------------------------------------------------------------------- 1 | import static org.gradle.internal.logging.text.StyledTextOutput.Style 2 | 3 | import org.apereo.cas.metadata.* 4 | import org.gradle.internal.logging.text.* 5 | 6 | import groovy.json.* 7 | import groovy.time.* 8 | import groovy.xml.* 9 | 10 | import java.nio.file.* 11 | import java.util.* 12 | import java.security.* 13 | 14 | buildscript { 15 | repositories { 16 | mavenLocal() 17 | mavenCentral() 18 | gradlePluginPortal() 19 | maven { 20 | url = 'https://central.sonatype.com/repository/maven-snapshots/' 21 | mavenContent { snapshotsOnly() } 22 | } 23 | maven { 24 | url = "https://repo.spring.io/milestone" 25 | mavenContent { releasesOnly() } 26 | } 27 | } 28 | dependencies { 29 | classpath "org.apache.ivy:ivy:${project.ivyVersion}" 30 | classpath "org.apereo.cas:cas-server-support-configuration-metadata-repository:${project.'cas.version'}" 31 | } 32 | } 33 | apply plugin: "de.undercouch.download" 34 | 35 | tasks.register('debug', JavaExec) { 36 | group = "CAS" 37 | description = "Debug the CAS web application in embedded container mode on port 5005" 38 | jvmArgs = [ 39 | "-server", 40 | "-Xmx2048M", 41 | "-XX:+TieredCompilation", 42 | "-XX:TieredStopAtLevel=1" 43 | ] 44 | debug = true 45 | systemProperties = System.properties 46 | classpath = files("$buildDir/libs/cas.war") 47 | } 48 | tasks.register('run', JavaExec) { 49 | group = "CAS" 50 | description = "Run the CAS web application in embedded container mode" 51 | jvmArgs = [ 52 | "-server", 53 | "-Xmx2048M", 54 | "-XX:+TieredCompilation", 55 | "-XX:TieredStopAtLevel=1" 56 | ] 57 | debug = false 58 | systemProperties = System.properties 59 | classpath = files("$buildDir/libs/cas.war") 60 | } 61 | 62 | 63 | 64 | task showConfiguration(group: "CAS", description: "Show configurations for each dependency, etc") { 65 | doLast() { 66 | def cfg = project.hasProperty("configuration") ? project.property("configuration") : "compile" 67 | configurations.getByName(cfg).each { println it } 68 | } 69 | } 70 | 71 | task allDependenciesInsight(group: "build", type: DependencyInsightReportTask, description: "Produce insight information for all dependencies") {} 72 | 73 | task allDependencies(group: "build", type: DependencyReportTask, description: "Display a graph of all project dependencies") {} 74 | 75 | task casVersion(group: "CAS", description: "Display the current CAS version") { 76 | doFirst { 77 | def verbose = project.hasProperty("verbose") && Boolean.valueOf(project.getProperty("verbose")) 78 | if (verbose) { 79 | def out = services.get(StyledTextOutputFactory).create("CAS") 80 | println "******************************************************************" 81 | out.withStyle(Style.Info).println "Apereo CAS ${project.version}" 82 | out.withStyle(Style.Description).println "Enterprise Single SignOn for all earthlings and beyond" 83 | out.withStyle(Style.SuccessHeader).println "- GitHub: " 84 | out.withStyle(Style.Success).println "https://github.com/apereo/cas" 85 | out.withStyle(Style.SuccessHeader).println "- Docs: " 86 | out.withStyle(Style.Success).println "https://apereo.github.io/cas" 87 | out.withStyle(Style.SuccessHeader).println "- Blog: " 88 | out.withStyle(Style.Success).println "https://apereo.github.io" 89 | println "******************************************************************" 90 | } else { 91 | println project.version 92 | } 93 | } 94 | } 95 | 96 | task springBootVersion(description: "Display current Spring Boot version") { 97 | doLast { 98 | println rootProject.springBootVersion 99 | } 100 | } 101 | 102 | task zip(type: Zip, description: "Package the CAS overlay into a zip archive", group: "CAS") { 103 | from projectDir 104 | exclude '**/.idea/**', '.gradle', 'tmp', '.git', '**/build/**', '**/bin/**', '**/out/**', '**/.settings/**' 105 | destinationDirectory = buildDir 106 | archiveFileName = "${project.name}.zip" 107 | def zipFile = new File("${buildDir}/${archiveFileName.get()}") 108 | doLast { 109 | if (zipFile.exists()) { 110 | println "Zip archive is available at ${zipFile.absolutePath}" 111 | } 112 | } 113 | } 114 | 115 | task createKeystore(group: "CAS", description: "Create CAS keystore") { 116 | def dn = "CN=cas.example.org,OU=Example,OU=Org,C=US" 117 | if (project.hasProperty("certificateDn")) { 118 | dn = project.getProperty("certificateDn") 119 | } 120 | def subjectAltName = "dns:example.org,dns:localhost,ip:127.0.0.1" 121 | if (project.hasProperty("certificateSubAltName")) { 122 | subjectAltName = project.getProperty("certificateSubAltName") 123 | } 124 | 125 | doFirst { 126 | def certDir = project.getProperty("certDir") 127 | def serverKeyStore = project.getProperty("serverKeystore") 128 | def exportedServerCert = project.getProperty("exportedServerCert") 129 | def storeType = project.getProperty("storeType") 130 | def keystorePath = "$certDir/$serverKeyStore" 131 | def serverCert = "$certDir/$exportedServerCert" 132 | 133 | mkdir certDir 134 | // this will fail if thekeystore exists and has cert with cas alias already (so delete if you want to recreate) 135 | logger.info "Generating keystore for CAS with DN ${dn}" 136 | exec { 137 | workingDir "." 138 | commandLine "keytool", "-genkeypair", "-alias", "cas", 139 | "-keyalg", "RSA", 140 | "-keypass", "changeit", "-storepass", "changeit", 141 | "-keystore", keystorePath, 142 | "-dname", dn, "-ext", "SAN=${subjectAltName}", 143 | "-storetype", storeType 144 | } 145 | logger.info "Exporting cert from keystore..." 146 | exec { 147 | workingDir "." 148 | commandLine "keytool", "-exportcert", "-alias", "cas", 149 | "-storepass", "changeit", "-keystore", keystorePath, 150 | "-file", serverCert 151 | } 152 | logger.info "Import $serverCert into your Java truststore (\$JAVA_HOME/lib/security/cacerts)" 153 | } 154 | } 155 | 156 | task unzipWAR(type: Copy, group: "CAS", description: "Explodes the CAS web application archive") { 157 | dependsOn 'build' 158 | def destination = "${buildDir}/app" 159 | 160 | from zipTree("build/libs/cas.war") 161 | into "${destination}" 162 | doLast { 163 | println "Unzipped WAR into ${destination}" 164 | } 165 | } 166 | 167 | task verifyRequiredJavaVersion { 168 | def currentVersion = org.gradle.api.JavaVersion.current() 169 | logger.info "Checking current Java version ${currentVersion} for required Java version ${project.targetCompatibility}" 170 | def targetVersion = JavaVersion.toVersion(project.targetCompatibility) 171 | if (!currentVersion.isCompatibleWith(targetVersion)) { 172 | logger.warn("Careful: Current Java version ${currentVersion} does not match required Java version ${project.targetCompatibility}") 173 | } 174 | } 175 | 176 | task copyCasConfiguration(type: Copy, group: "CAS", 177 | description: "Copy the CAS configuration from this project to /etc/cas/config") { 178 | from "etc/cas/config" 179 | into new File('/etc/cas/config').absolutePath 180 | doFirst { 181 | new File('/etc/cas/config').mkdirs() 182 | } 183 | } 184 | 185 | 186 | def explodedDir = "${buildDir}/app" 187 | def explodedResourcesDir = "${buildDir}/cas-resources" 188 | 189 | def resourcesJarName = "cas-server-webapp-resources" 190 | def templateViewsJarName = "cas-server-support-thymeleaf" 191 | 192 | task unzip(type: Copy, group: "CAS", description: "Explodes the CAS archive and resources jar from the CAS web application archive") { 193 | dependsOn unzipWAR 194 | from zipTree("${explodedDir}/WEB-INF/lib/${templateViewsJarName}-${project.'cas.version'}.jar") 195 | into explodedResourcesDir 196 | 197 | from zipTree("${explodedDir}/WEB-INF/lib/${resourcesJarName}-${project.'cas.version'}.jar") 198 | into explodedResourcesDir 199 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 200 | doLast { 201 | println "Exploded WAR resources into ${explodedResourcesDir}" 202 | } 203 | } 204 | 205 | task downloadShell(group: "Shell", description: "Download CAS shell jar from snapshot or release maven repo", type: Download) { 206 | def shellDir = project.providers.gradleProperty("shellDir").get() 207 | def casVersion = project.providers.gradleProperty("cas.version").get() 208 | def downloadFile 209 | if (casVersion.contains("-SNAPSHOT")) { 210 | def snapshotDir = "https://central.sonatype.com/repository/maven-snapshots/org/apereo/cas/cas-server-support-shell/${casVersion}" 211 | def metaUrl = "${snapshotDir}/maven-metadata.xml" 212 | def xml = new XmlSlurper().parse(metaUrl.toURL().openStream()) 213 | def sv = xml.versioning.snapshotVersions.snapshotVersion.findAll { 214 | it.extension.text() == "jar" && it.classifier.text() == "" 215 | }.sort { a, b -> a.updated.text() <=> b.updated.text() }.last() 216 | def stamped = sv.value.text() 217 | def jarName = "cas-server-support-shell-${stamped}.jar" 218 | def jarUrl = "${snapshotDir}/${jarName}" 219 | downloadFile = jarUrl 220 | } else { 221 | downloadFile = "https://github.com/apereo/cas/releases/download/v${casVersion}/cas-server-support-shell-${casVersion}.jar" 222 | } 223 | new File("${shellDir}").mkdir() 224 | logger.info "Downloading file: ${downloadFile} into ${shellDir}" 225 | src downloadFile 226 | dest new File("${shellDir}", "cas-server-support-shell-${casVersion}.jar") 227 | overwrite false 228 | } 229 | 230 | task runShell(group: "Shell", description: "Run the CAS shell") { 231 | dependsOn downloadShell 232 | def shellDir = project.providers.gradleProperty("shellDir").get() 233 | def casVersion = project.providers.gradleProperty("cas.version").get() 234 | doLast { 235 | println "Run the following command to launch the shell:\n\tjava -jar ${shellDir}/cas-server-support-shell-${casVersion}.jar" 236 | } 237 | } 238 | 239 | task debugShell(group: "Shell", description: "Run the CAS shell with debug options, wait for debugger on port 5005") { 240 | dependsOn downloadShell 241 | def casVersion = project.providers.gradleProperty("cas.version").get() 242 | def shellDir = project.providers.gradleProperty("shellDir").get() 243 | doLast { 244 | println """ 245 | Run the following command to launch the shell:\n\t 246 | java -Xrunjdwp:transport=dt_socket,address=5000,server=y,suspend=y -jar ${shellDir}/cas-server-support-shell-${casVersion}.jar 247 | """ 248 | } 249 | } 250 | 251 | task listTemplateViews(group: "CAS", description: "List all CAS views") { 252 | dependsOn unzip 253 | 254 | def templateViews = fileTree(explodedResourcesDir).matching { 255 | include "**/*.html" 256 | } 257 | .collect { 258 | return it.path.replace(explodedResourcesDir, "") 259 | } 260 | .toSorted() 261 | 262 | doFirst { 263 | templateViews.each { println it } 264 | } 265 | } 266 | 267 | task getResource(group: "CAS", description: "Fetch a CAS resource and move it into the overlay") { 268 | dependsOn unzip 269 | 270 | def resourceName = project.providers.gradleProperty("resourceName").getOrNull() 271 | def resourcesDirectory = fileTree(explodedResourcesDir) 272 | def projectDirectory = projectDir 273 | 274 | doFirst { 275 | def results = resourcesDirectory.matching { 276 | include "**/${resourceName}.*" 277 | include "**/${resourceName}" 278 | } 279 | if (results.isEmpty()) { 280 | println "No resources could be found matching ${resourceName}" 281 | return 282 | } 283 | if (results.size() > 1) { 284 | println "Multiple resources found matching ${resourceName}:\n" 285 | results.each { 286 | println "\t-" + it.path.replace(explodedResourcesDir, "") 287 | } 288 | println "\nNarrow down your search criteria and try again." 289 | return 290 | } 291 | 292 | def fromFile = explodedResourcesDir 293 | def resourcesDir = "src/main/resources" 294 | new File(resourcesDir).mkdir() 295 | 296 | def resourceFile = results[0].canonicalPath 297 | def toResourceFile = new File("${projectDirectory}", resourceFile.replace(fromFile, resourcesDir)) 298 | toResourceFile.getParentFile().mkdirs() 299 | 300 | Files.copy(Paths.get(resourceFile), Paths.get(toResourceFile.absolutePath), StandardCopyOption.REPLACE_EXISTING) 301 | println "Copied file ${resourceFile} to ${toResourceFile}" 302 | } 303 | } 304 | 305 | task createTheme(group: "CAS", description: "Create theme directory structure in the overlay") { 306 | def theme = project.providers.gradleProperty("theme").getOrNull() 307 | 308 | doFirst { 309 | def builder = new FileTreeBuilder() 310 | new File("src/main/resources/${theme}.properties").delete() 311 | 312 | builder.src { 313 | main { 314 | resources { 315 | "static" { 316 | themes { 317 | "${theme}" { 318 | css { 319 | 'cas.css'('') 320 | } 321 | js { 322 | 'cas.js'('') 323 | } 324 | images { 325 | '.ignore'('') 326 | } 327 | } 328 | } 329 | } 330 | 331 | templates { 332 | "${theme}" { 333 | fragments { 334 | 335 | } 336 | } 337 | } 338 | 339 | "${theme}.properties"("""cas.standard.css.file=/themes/${theme}/css/cas.css 340 | cas.standard.js.file=/themes/${theme}/js/cas.js 341 | """) 342 | } 343 | } 344 | } 345 | } 346 | } 347 | 348 | def skipValidation = project.hasProperty("validate") && project.property("validate").equals("false") 349 | if (!skipValidation) { 350 | task validateConfiguration(type: Copy, group: "CAS", 351 | description: "Validate CAS configuration") { 352 | def file = new File("${projectDir}/src/main/resources/application.properties") 353 | if (file.exists()) { 354 | throw new GradleException("This overlay project is overriding a CAS-supplied configuration file at ${file.path}. " 355 | + "Overriding this file will disable all default CAS settings that are provided to the overlay, and " 356 | + "generally has unintended side-effects. It's best to move your configuration inside an application.yml " 357 | + "file, if you intend to keep the configuration bundled with the CAS web application. \n\nTo disable this " 358 | + "validation step, run the build with -Pvalidate=false."); 359 | } 360 | } 361 | processResources.dependsOn(validateConfiguration) 362 | } 363 | 364 | task duct(group: "CAS", description: "Test ticket registry functionality via the CAS REST API") { 365 | def service = project.findProperty("duct.service") ?: "https://apereo.github.io" 366 | def casServerNodes = providers.gradlePropertiesPrefixedBy("duct.cas").get() 367 | def username = project.findProperty("duct.username") ?: "casuser" 368 | def password = project.findProperty("duct.password") ?: "Mellon" 369 | def debug = Boolean.parseBoolean(project.findProperty("duct.debug") ?: "false") 370 | def duration = Long.parseLong(project.findProperty("duct.duration") ?: "-1") 371 | def count = Long.parseLong(project.findProperty("duct.count") ?: "-1") 372 | 373 | doLast { 374 | def out = services.get(StyledTextOutputFactory).create("cas") 375 | 376 | def getCasServerNode = { 377 | def casServerNodesArray = casServerNodes.values().toArray() 378 | return casServerNodesArray[new SecureRandom().nextInt(casServerNodesArray.length)] as String 379 | } 380 | 381 | def startTime = new Date() 382 | def keepGoing = true 383 | def executionCount = 0 384 | 385 | while(keepGoing) { 386 | executionCount++ 387 | 388 | def casServerPrefix1 = getCasServerNode() 389 | def casServerPrefix2 = getCasServerNode() 390 | 391 | if (casServerNodes.size() >= 2) { 392 | while (casServerPrefix1.equals(casServerPrefix2)) { 393 | casServerPrefix2 = getCasServerNode() 394 | } 395 | } 396 | 397 | if (debug) { 398 | out.withStyle(Style.Normal).println("CAS Server 1: ${casServerPrefix1}") 399 | out.withStyle(Style.Normal).println("CAS Server 2: ${casServerPrefix2}") 400 | out.withStyle(Style.Normal).println("Fetching ticket-granting ticket @ ${casServerPrefix1} for ${username}...") 401 | } 402 | def connection = new URL("${casServerPrefix1}/v1/tickets").openConnection() 403 | connection.setRequestMethod("POST") 404 | connection.setDoOutput(true) 405 | connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded") 406 | connection.getOutputStream().write("username=${username}&password=${password}".getBytes("UTF-8")) 407 | def rc = connection.getResponseCode() 408 | 409 | if (rc == 201) { 410 | def tgt = connection.getHeaderFields().get("Location").get(0).find('TGT-.*') 411 | 412 | if (debug) { 413 | out.withStyle(Style.Normal).println("Received ticket-granting ticket ${tgt} @ ${casServerPrefix2} for ${username}...") 414 | out.withStyle(Style.Normal).println("Fetching service ticket @ ${casServerPrefix2} for ${tgt} and service ${service}...") 415 | } 416 | connection = new URL("${casServerPrefix2}/v1/tickets/${tgt}").openConnection() 417 | connection.setRequestMethod("POST") 418 | connection.setDoOutput(true) 419 | connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded") 420 | connection.getOutputStream().write("service=${service}".getBytes("UTF-8")) 421 | rc = connection.getResponseCode() 422 | 423 | if (rc == 200) { 424 | def st = connection.getInputStream().getText() 425 | if (debug) { 426 | out.withStyle(Style.Normal).println("Received service ticket ${st} @ ${casServerPrefix1} for ${service}...") 427 | out.withStyle(Style.Normal).println("Validating service ticket ${st} @ ${casServerPrefix1} for service ${service}...") 428 | } 429 | connection = new URL("${casServerPrefix1}/p3/serviceValidate?service=${service}&ticket=${st}&format=json").openConnection() 430 | connection.setRequestMethod("GET") 431 | connection.setDoOutput(true) 432 | connection.setRequestProperty("Content-Type", "application/json") 433 | rc = connection.getResponseCode() 434 | 435 | if (rc == 200) { 436 | def serverResponse = connection.getInputStream().getText() 437 | def response = new JsonSlurper().parseText(serverResponse) 438 | 439 | if (response.serviceResponse["authenticationSuccess"] != null) { 440 | out.withStyle(Style.Success).println("Service ticket ${st} is successfully validated @ ${casServerPrefix1}") 441 | } else { 442 | out.withStyle(Style.Failure).println("Service ticket ${st} cannot be validated @ ${casServerPrefix1} for ${tgt}") 443 | if (debug) { 444 | out.withStyle(Style.Failure).println(serverResponse) 445 | } 446 | } 447 | } else { 448 | out.withStyle(Style.Failure).println("${rc}: Unable to validate service ticket ${st} @ ${casServerPrefix1} for ${tgt}") 449 | } 450 | } else { 451 | out.withStyle(Style.Failure).println("${rc}: Unable to fetch service ticket @ ${casServerPrefix2} for ${tgt}") 452 | } 453 | } else { 454 | out.withStyle(Style.Failure).println("${rc}: Unable to fetch ticket-granting ticket @ ${casServerPrefix1}") 455 | } 456 | 457 | if (keepGoing && duration > 0) { 458 | def executionDuration = TimeCategory.minus(new Date(), startTime) 459 | keepGoing = executionDuration.getSeconds() < duration 460 | } 461 | if (keepGoing) { 462 | keepGoing = executionCount < count 463 | } 464 | Thread.sleep(250) 465 | } 466 | } 467 | } 468 | 469 | task exportConfigMetadata(group: "CAS", description: "Export collection of CAS properties") { 470 | def file = new File(project.rootDir, 'config-metadata.properties') 471 | def queryType = ConfigurationMetadataCatalogQuery.QueryTypes.CAS 472 | if (project.hasProperty("queryType")) { 473 | queryType = ConfigurationMetadataCatalogQuery.QueryTypes.valueOf(project.findProperty("queryType")) 474 | } 475 | doLast { 476 | file.withWriter('utf-8') { writer -> 477 | def props = CasConfigurationMetadataCatalog.query( 478 | ConfigurationMetadataCatalogQuery.builder() 479 | .queryType(queryType) 480 | .build()) 481 | .properties() 482 | props.each { property -> 483 | writer.writeLine("# Type: ${property.type}"); 484 | writer.writeLine("# Module: ${property.module}") 485 | writer.writeLine("# Owner: ${property.owner}") 486 | if (property.deprecationLevel != null) { 487 | writer.writeLine("# This setting is deprecated with a severity level of ${property.deprecationLevel}.") 488 | if (property.deprecationReason != null) { 489 | writer.writeLine("# because ${property.deprecationReason}") 490 | } 491 | if (property.deprecationReason != null) { 492 | writer.writeLine("# Replace with: ${property.deprecationReason}") 493 | } 494 | } 495 | writer.writeLine("#") 496 | def description = property.description.replace("\n", "\n# ").replace("\r", "") 497 | description = org.apache.commons.text.WordUtils.wrap(description, 70, "\n# ", true) 498 | writer.writeLine("# ${description}") 499 | writer.writeLine("#") 500 | writer.writeLine("# ${property.name}: ${property.defaultValue}") 501 | writer.writeLine("") 502 | } 503 | } 504 | println "Configuration metadata is available at ${file.absolutePath}" 505 | } 506 | } 507 | --------------------------------------------------------------------------------