From fc2376903b2fce001df643d0cdd947d054f1d3c1 Mon Sep 17 00:00:00 2001 From: Cheng Liang Date: Thu, 4 Jul 2024 19:58:38 +0800 Subject: [PATCH 1/2] =?UTF-8?q?1.=E5=B0=86Device=20Flow=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E4=B8=BARFC=E6=A0=87=E5=87=86=202.=E7=A7=BB=E9=99=A4apereo-cas?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=A4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apereo-cas/.dockerignore | 7 - apereo-cas/.gitattributes | 6 - apereo-cas/.gitignore | 48 -- apereo-cas/Dockerfile | 45 - apereo-cas/LICENSE.txt | 202 ----- apereo-cas/Procfile | 1 - apereo-cas/README.md | 231 ------ apereo-cas/build.gradle | 146 ---- apereo-cas/build.gradle.backup | 150 ---- apereo-cas/docker-build.sh | 15 - apereo-cas/docker-compose.yml | 35 - apereo-cas/docker-push.sh | 15 - apereo-cas/docker-run.sh | 7 - apereo-cas/etc/cas/.ignore | 0 apereo-cas/etc/cas/config/cas.properties | 52 -- apereo-cas/etc/cas/config/log4j2.xml | 163 ---- apereo-cas/etc/cas/services/oauth-3001.json | 32 - .../etc/cas/services/oauth-pkce-3002.json | 11 - apereo-cas/gradle.properties | 59 -- apereo-cas/gradle/jib.gradle | 63 -- apereo-cas/gradle/springboot.gradle | 136 --- apereo-cas/gradle/tasks.gradle | 420 ---------- apereo-cas/gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - apereo-cas/gradlew | 244 ------ apereo-cas/gradlew.bat | 92 --- apereo-cas/helm/README.md | 114 --- apereo-cas/helm/cas-server/.helmignore | 23 - apereo-cas/helm/cas-server/Chart.yaml | 24 - .../helm/cas-server/templates/NOTES.txt | 31 - .../helm/cas-server/templates/_helpers.tpl | 187 ----- .../templates/bootadmin/configmap.yaml | 9 - .../templates/bootadmin/deployment.yaml | 188 ----- .../templates/bootadmin/ingress.yaml | 53 -- .../templates/bootadmin/service.yaml | 17 - .../templates/casconfig-configmap.yaml | 7 - .../helm/cas-server/templates/ingress.yaml | 53 -- .../cas-server/templates/mgmt/configmap.yaml | 9 - .../cas-server/templates/mgmt/deployment.yaml | 188 ----- .../cas-server/templates/mgmt/ingress.yaml | 53 -- .../cas-server/templates/mgmt/service.yaml | 17 - .../helm/cas-server/templates/role.yaml | 12 - .../cas-server/templates/rolebinding.yaml | 16 - .../templates/script-configmap.yaml | 54 -- .../helm/cas-server/templates/service.yaml | 15 - .../cas-server/templates/serviceaccount.yaml | 12 - .../cas-server/templates/statefulset.yaml | 263 ------ .../templates/tests/test-bootadmin.yaml | 16 - .../templates/tests/test-cas-server.yaml | 16 - apereo-cas/helm/cas-server/values.yaml | 782 ------------------ .../helm/create-cas-server-keystore-secret.sh | 20 - apereo-cas/helm/create-ingress-tls.sh | 19 - apereo-cas/helm/create-truststore.sh | 42 - apereo-cas/helm/delete-cas-server.sh | 3 - apereo-cas/helm/install-cas-server-example.sh | 5 - apereo-cas/helm/install-cas-server.sh | 4 - apereo-cas/helm/values-example1.yaml | 63 -- apereo-cas/lombok.config | 9 - apereo-cas/settings.gradle | 1 - .../CasOverlayOverrideConfiguration.java | 23 - apereo-cas/src/main/jib/docker/entrypoint.sh | 30 - .../main/resources/META-INF/spring.factories | 1 - apereo-cas/src/main/resources/application.yml | 9 - apereo-cas/src/main/webapp/WEB-INF/web.xml | 7 - apereo-cas/system.properties | 1 - controller/config.go | 2 + controller/oauth2_device_flow.go | 28 +- controller/route.go | 2 +- .../views/playground/components/Device.vue | 44 +- .../src/views/playground/components/PKCE.vue | 1 + .../src/views/playground/index.vue | 6 + g/cfg.go | 7 +- go.mod | 1 - go.sum | 2 - 74 files changed, 51 insertions(+), 4624 deletions(-) delete mode 100644 apereo-cas/.dockerignore delete mode 100644 apereo-cas/.gitattributes delete mode 100644 apereo-cas/.gitignore delete mode 100644 apereo-cas/Dockerfile delete mode 100644 apereo-cas/LICENSE.txt delete mode 100644 apereo-cas/Procfile delete mode 100644 apereo-cas/README.md delete mode 100644 apereo-cas/build.gradle delete mode 100644 apereo-cas/build.gradle.backup delete mode 100644 apereo-cas/docker-build.sh delete mode 100644 apereo-cas/docker-compose.yml delete mode 100644 apereo-cas/docker-push.sh delete mode 100644 apereo-cas/docker-run.sh delete mode 100644 apereo-cas/etc/cas/.ignore delete mode 100644 apereo-cas/etc/cas/config/cas.properties delete mode 100644 apereo-cas/etc/cas/config/log4j2.xml delete mode 100644 apereo-cas/etc/cas/services/oauth-3001.json delete mode 100644 apereo-cas/etc/cas/services/oauth-pkce-3002.json delete mode 100644 apereo-cas/gradle.properties delete mode 100644 apereo-cas/gradle/jib.gradle delete mode 100644 apereo-cas/gradle/springboot.gradle delete mode 100644 apereo-cas/gradle/tasks.gradle delete mode 100644 apereo-cas/gradle/wrapper/gradle-wrapper.jar delete mode 100644 apereo-cas/gradle/wrapper/gradle-wrapper.properties delete mode 100644 apereo-cas/gradlew delete mode 100644 apereo-cas/gradlew.bat delete mode 100644 apereo-cas/helm/README.md delete mode 100644 apereo-cas/helm/cas-server/.helmignore delete mode 100644 apereo-cas/helm/cas-server/Chart.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/NOTES.txt delete mode 100644 apereo-cas/helm/cas-server/templates/_helpers.tpl delete mode 100644 apereo-cas/helm/cas-server/templates/bootadmin/configmap.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/bootadmin/deployment.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/bootadmin/ingress.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/bootadmin/service.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/casconfig-configmap.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/ingress.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/mgmt/configmap.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/mgmt/deployment.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/mgmt/ingress.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/mgmt/service.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/role.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/rolebinding.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/script-configmap.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/service.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/serviceaccount.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/statefulset.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/tests/test-bootadmin.yaml delete mode 100644 apereo-cas/helm/cas-server/templates/tests/test-cas-server.yaml delete mode 100644 apereo-cas/helm/cas-server/values.yaml delete mode 100644 apereo-cas/helm/create-cas-server-keystore-secret.sh delete mode 100644 apereo-cas/helm/create-ingress-tls.sh delete mode 100644 apereo-cas/helm/create-truststore.sh delete mode 100644 apereo-cas/helm/delete-cas-server.sh delete mode 100644 apereo-cas/helm/install-cas-server-example.sh delete mode 100644 apereo-cas/helm/install-cas-server.sh delete mode 100644 apereo-cas/helm/values-example1.yaml delete mode 100644 apereo-cas/lombok.config delete mode 100644 apereo-cas/settings.gradle delete mode 100644 apereo-cas/src/main/java/org/apereo/cas/config/CasOverlayOverrideConfiguration.java delete mode 100644 apereo-cas/src/main/jib/docker/entrypoint.sh delete mode 100644 apereo-cas/src/main/resources/META-INF/spring.factories delete mode 100644 apereo-cas/src/main/resources/application.yml delete mode 100644 apereo-cas/src/main/webapp/WEB-INF/web.xml delete mode 100644 apereo-cas/system.properties diff --git a/apereo-cas/.dockerignore b/apereo-cas/.dockerignore deleted file mode 100644 index 5e04587..0000000 --- a/apereo-cas/.dockerignore +++ /dev/null @@ -1,7 +0,0 @@ -target/** -build/** -bin/** -.idea/** -.history/** -.github/** -.git/** diff --git a/apereo-cas/.gitattributes b/apereo-cas/.gitattributes deleted file mode 100644 index 8fc5677..0000000 --- a/apereo-cas/.gitattributes +++ /dev/null @@ -1,6 +0,0 @@ -# Set line endings to LF, even on Windows. Otherwise, execution within Docker fails. -# See https://help.github.com/articles/dealing-with-line-endings/ -*.sh text eol=lf -gradlew text eol=lf -*.cmd text eol=crlf -*.bat text eol=crlf diff --git a/apereo-cas/.gitignore b/apereo-cas/.gitignore deleted file mode 100644 index 5c6b8cf..0000000 --- a/apereo-cas/.gitignore +++ /dev/null @@ -1,48 +0,0 @@ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ - -### VS Code ### -.vscode/ -.classpath -!/.project -.project -.settings -.history -.vscode -target/ -.idea/ -.DS_Store -.idea -overlays/ -.gradle/ -build/ -log/ -bin/ -*.war -*.iml -*.log -tmp/ -.java-version -./apache-tomcat -apache-tomcat.zip -config-metadata.properties diff --git a/apereo-cas/Dockerfile b/apereo-cas/Dockerfile deleted file mode 100644 index 5047d53..0000000 --- a/apereo-cas/Dockerfile +++ /dev/null @@ -1,45 +0,0 @@ -FROM eclipse-temurin:11-jdk AS overlay - -RUN mkdir -p cas-overlay -COPY ./src cas-overlay/src/ -COPY ./gradle/ cas-overlay/gradle/ -COPY ./gradlew ./settings.gradle ./build.gradle ./gradle.properties ./lombok.config /cas-overlay/ - -RUN mkdir -p ~/.gradle \ - && echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties \ - && echo "org.gradle.configureondemand=true" >> ~/.gradle/gradle.properties \ - && cd cas-overlay \ - && chmod 750 ./gradlew \ - && ./gradlew --version; - -RUN cd cas-overlay \ - && ./gradlew clean build --parallel --no-daemon; - -FROM eclipse-temurin:11-jdk AS cas - -LABEL "Organization"="Apereo" -LABEL "Description"="Apereo CAS" - -# 安装 sqlite3 -RUN apt-get update \ - && apt-get install -y --no-install-recommends sqlite3 \ - && rm -rf /var/lib/apt/lists/* - -RUN cd / \ - && mkdir -p /etc/cas/config \ - && mkdir -p /etc/cas/services \ - && mkdir -p /etc/cas/saml \ - && mkdir -p cas-overlay; - -COPY --from=overlay cas-overlay/build/libs/cas.war cas-overlay/ -COPY etc/cas/ /etc/cas/ -COPY etc/cas/config/ /etc/cas/config/ -COPY etc/cas/services/ /etc/cas/services/ -COPY etc/cas/saml/ /etc/cas/saml/ - -EXPOSE 8080 8443 - -ENV PATH $PATH:$JAVA_HOME/bin:. - -WORKDIR cas-overlay -ENTRYPOINT ["java", "-server", "-noverify", "-Xmx2048M", "-jar", "cas.war"] diff --git a/apereo-cas/LICENSE.txt b/apereo-cas/LICENSE.txt deleted file mode 100644 index d645695..0000000 --- a/apereo-cas/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/apereo-cas/Procfile b/apereo-cas/Procfile deleted file mode 100644 index 2c732c3..0000000 --- a/apereo-cas/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: java $JAVA_OPTS -jar build/libs/cas.war --server.port=$PORT --server.ssl.enabled=false diff --git a/apereo-cas/README.md b/apereo-cas/README.md deleted file mode 100644 index a55ff94..0000000 --- a/apereo-cas/README.md +++ /dev/null @@ -1,231 +0,0 @@ -# IMPORTANT NOTE
******************************************************
This repository is always automatically generated from the 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).
******************************************************
-Apereo CAS WAR Overlay Template -===================================== - -WAR Overlay Type: `cas-overlay` - -# Versions - - -- CAS Server `6.5.9` -- JDK `11` - -# Build - -To build the project, use: - -```bash -# Use --refresh-dependencies to force-update SNAPSHOT versions -./gradlew[.bat] clean build -``` - -To see what commands/tasks are available to the build script, run: - -```bash -./gradlew[.bat] tasks -``` - -If you need to, on Linux/Unix systems, you can delete all the existing artifacts -(artifacts and metadata) Gradle has downloaded using: - -```bash -# Only do this when absolutely necessary -rm -rf $HOME/.gradle/caches/ -``` - -Same strategy applies to Windows too, provided you switch `$HOME` to its equivalent in the above command. - -# Keystore - -For the server to run successfully, you might need to create a keystore file. -This can either be done using the JDK's `keytool` utility or via the following command: - -```bash -./gradlew[.bat] createKeystore -``` - -Use the password `changeit` for both the keystore and the key/certificate entries. -Ensure the keystore is loaded up with keys and certificates of the server. - -## Extension Modules - -Extension modules may be specified under the `dependencies` block of the [Gradle build script](build.gradle): - -```gradle -dependencies { - implementation "org.apereo.cas:cas-server-some-module" - ... -} -``` - -To collect the list of all project modules and dependencies in the overlay: - -```bash -./gradlew[.bat] dependencies -``` - -To see a full list of all project dependencies that are available for configuration and use: - -```bash -curl https://localhost:8080/dependencies -``` - -Or: - -```bash -curl https://localhost:8080/actuator/info -``` - -# Deployment - -On a successful deployment via the following methods, the server will be available at: - - -* `https://localhost:8443/cas` - - - - -## Executable WAR - -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. - -The current servlet container is specified as `-tomcat`. - -```bash -java -jar build/libs/cas.war -``` - -Or via: - -```bash -./gradlew[.bat] run -``` - -Debug the CAS web application as an executable WAR: - -```bash -./gradlew[.bat] debug -``` - -Or via: - -```bash -java -Xdebug -Xrunjdwp:transport=dt_socket,address=5000,server=y,suspend=y -jar build/libs/cas.war -``` - -Run the CAS web application as a *standalone* executable WAR: - -```bash -./gradlew[.bat] clean executable -``` - -## External - -Deploy the binary web application file in `build/libs` after a successful build to a servlet container of choice. - -# Docker - -The following strategies outline how to build and deploy CAS Docker images. - -## Jib - -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. - -```bash -# Running this task requires that you have Docker installed and running. -./gradlew build jibDockerBuild -``` - -## Dockerfile - -You can also use the native Docker tooling and the provided `Dockerfile` to build and run. - -```bash -chmod +x *.sh -./docker-build.sh -./docker-run.sh -``` - -For convenience, an additional `docker-compose.yml` is also provided to orchestrate the build: - -```bash -docker-compose build -``` - - -# CAS Command-line Shell - -To launch into the CAS command-line shell: - -```bash -./gradlew[.bat] downloadShell runShell -``` - -# Retrieve Overlay Resources - -To fetch and overlay a CAS resource or view, use: - -```bash -./gradlew[.bat] getResource -PresourceName=[resource-name] -``` - -# Create User Interface Themes Structure - -You can use the overlay to construct the correct directory structure for custom user interface themes: - -```bash -./gradlew[.bat] createTheme -Ptheme=redbeard -``` - -The generated directory structure should match the following: - -``` -├── redbeard.properties -├── static -│ └── themes -│ └── redbeard -│ ├── css -│ │ └── cas.css -│ └── js -│ └── cas.js -└── templates - └── redbeard - └── fragments -``` - -HTML templates and fragments can be moved into the above directory structure, -and the theme may be assigned to applications for use. - -# List Overlay Resources - -To list all available CAS views and templates: - -```bash -./gradlew[.bat] listTemplateViews -``` - -To unzip and explode the CAS web application file and the internal resources jar: - -```bash -./gradlew[.bat] explodeWar -``` - -# Configuration - -- The `etc` directory contains the configuration files and directories that need to be copied to `/etc/cas/config`. - -```bash -./gradlew[.bat] copyCasConfiguration -``` - -- The specifics of the build are controlled using the `gradle.properties` file. - -## Configuration Metadata - -Configuration metadata allows you to export collection of CAS properties as a report into a file -that can later be examined. You will find a full list of CAS settings along with notes, types, default and accepted values: - -```bash -./gradlew exportConfigMetadata -``` diff --git a/apereo-cas/build.gradle b/apereo-cas/build.gradle deleted file mode 100644 index 024e37d..0000000 --- a/apereo-cas/build.gradle +++ /dev/null @@ -1,146 +0,0 @@ -import org.apache.tools.ant.taskdefs.condition.* -import org.gradle.internal.logging.text.* -import org.apereo.cas.metadata.* -import java.nio.file.* -import static org.gradle.internal.logging.text.StyledTextOutput.Style - -buildscript { - repositories { - // maven { - // url 'https//maven.aliyun.com/nexus/content/groups/public/' - // } - // maven { - // url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' - // } - // maven { name "Alibaba" ; url 'https://maven.aliyun.com/repository/public' } - // maven { name "Bstek" ; url 'https://nexus.bsdn.org/content/groups/public/' } - // maven { name "M2" ; url 'https://plugins.gradle.org/m2/' } - maven { url "https://maven.aliyun.com/repository/central" } - maven { url "https://maven.aliyun.com/repository/public" } - maven { url "https://maven.aliyun.com/repository/google" } - maven { url "https://maven.aliyun.com/repository/gradle-plugin" } - maven { url 'https://jitpack.io' } - mavenCentral() - jcenter() - google() - } - dependencies { - classpath "org.springframework.boot:spring-boot-gradle-plugin:${project.springBootVersion}" - classpath "io.freefair.gradle:maven-plugin:${project.gradleFreeFairPluginVersion}" - classpath "io.freefair.gradle:lombok-plugin:${project.gradleFreeFairPluginVersion}" - classpath "io.spring.gradle:dependency-management-plugin:${project.gradleDependencyManagementPluginVersion}" - classpath "com.google.cloud.tools:jib-gradle-plugin:${project.jibVersion}" - - classpath "de.undercouch:gradle-download-task:${project.gradleDownloadTaskVersion}" - classpath "org.apereo.cas:cas-server-core-api-configuration-model:${project.'cas.version'}" - classpath "org.apereo.cas:cas-server-core-configuration-metadata-repository:${project.'cas.version'}" - } -} - -repositories { - // maven { - // url 'https://maven.aliyun.com/nexus/content/groups/public/' - // } - // maven { - // url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' - // } - // maven { name "Alibaba" ; url 'https://maven.aliyun.com/repository/public' } - // maven { name "Bstek" ; url 'https://nexus.bsdn.org/content/groups/public/' } - // maven { name "M2" ; url 'https://plugins.gradle.org/m2/' } - maven { url "https://maven.aliyun.com/repository/central" } - maven { url "https://maven.aliyun.com/repository/public" } - maven { url "https://maven.aliyun.com/repository/google" } - maven { url "https://maven.aliyun.com/repository/gradle-plugin" } - maven { url 'https://jitpack.io' } - mavenCentral() - jcenter() - google() -} - -apply plugin: "io.freefair.war-overlay" -apply plugin: "war" -apply plugin: "org.springframework.boot" -apply plugin: "io.freefair.lombok" - -apply from: rootProject.file("gradle/springboot.gradle") -apply from: rootProject.file("gradle/jib.gradle") -apply from: rootProject.file("gradle/tasks.gradle") - - -configurations.all { - resolutionStrategy { - cacheChangingModulesFor 0, "seconds" - cacheDynamicVersionsFor 0, "seconds" - preferProjectModules() - def failIfConflict = project.hasProperty("failOnVersionConflict") && Boolean.valueOf(project.getProperty("failOnVersionConflict")) - if (failIfConflict) { - failOnVersionConflict() - } - } -} - -war { - entryCompression = ZipEntryCompression.STORED - enabled = false -} - -sourceSets { - bootRunSources { - resources { - srcDirs new File("//etc/cas/templates/"), new File("${project.getProjectDir()}/src/main/resources/") - } - } -} - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(project.targetCompatibility) - } -} - -bootBuildImage { - imageName = "${project.'containerImageOrg'}/${project.'containerImageName'}:${project.version}" -} - -dependencies { - /** - * Do NOT modify the lines below or else you will risk breaking dependency management. - */ - implementation enforcedPlatform("org.apereo.cas:cas-server-support-bom:${project.'cas.version'}") - implementation platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) - - /** - * CAS dependencies and modules may be listed here. - * - * There is no need to specify the version number for each dependency - * since versions are all resolved and controlled by the dependency management - * plugin via the CAS bom. - **/ - - implementation "org.apereo.cas:cas-server-core-api-configuration-model" - implementation "org.apereo.cas:cas-server-webapp-init" - - implementation "org.apereo.cas:cas-server-support-jdbc:${project.'cas.version'}" - implementation "org.apereo.cas:cas-server-support-jdbc-drivers:${project.'cas.version'}" - implementation 'org.xerial:sqlite-jdbc' - - implementation "org.apereo.cas:cas-server-support-oauth-webflow:${project.'cas.version'}" - - - if (project.hasProperty("casModules")) { - def dependencies = project.getProperty("casModules").split(",") - dependencies.each { - def projectsToAdd = rootProject.subprojects.findAll {project -> - project.name == "cas-server-core-${it}" || project.name == "cas-server-support-${it}" - } - projectsToAdd.each {implementation it} - } - } - - - - - - developmentOnly "org.springframework.boot:spring-boot-devtools:${project.springBootVersion}" -} - diff --git a/apereo-cas/build.gradle.backup b/apereo-cas/build.gradle.backup deleted file mode 100644 index dffb043..0000000 --- a/apereo-cas/build.gradle.backup +++ /dev/null @@ -1,150 +0,0 @@ -import org.apache.tools.ant.taskdefs.condition.* -import org.gradle.internal.logging.text.* -import org.apereo.cas.metadata.* -import java.nio.file.* -import static org.gradle.internal.logging.text.StyledTextOutput.Style - -buildscript { - repositories { - # if (project.privateRepoUrl) { - # maven { - # url project.privateRepoUrl - # credentials { - # username = project.privateRepoUsername - # password = System.env.PRIVATE_REPO_TOKEN - # } - # } - # } - # mavenLocal() - # mavenCentral() - # gradlePluginPortal() - maven { - url 'http://maven.aliyun.com/nexus/content/groups/public/' - # url 'https://oss.sonatype.org/content/repositories/snapshots' - # mavenContent { snapshotsOnly() } - } - maven { - url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' - # url "https://repo.spring.io/milestone" - # mavenContent { releasesOnly() } - } - } - dependencies { - classpath "org.springframework.boot:spring-boot-gradle-plugin:${project.springBootVersion}" - classpath "io.freefair.gradle:maven-plugin:${project.gradleFreeFairPluginVersion}" - classpath "io.freefair.gradle:lombok-plugin:${project.gradleFreeFairPluginVersion}" - classpath "io.spring.gradle:dependency-management-plugin:${project.gradleDependencyManagementPluginVersion}" - classpath "com.google.cloud.tools:jib-gradle-plugin:${project.jibVersion}" - - classpath "de.undercouch:gradle-download-task:${project.gradleDownloadTaskVersion}" - classpath "org.apereo.cas:cas-server-core-api-configuration-model:${project.'cas.version'}" - classpath "org.apereo.cas:cas-server-core-configuration-metadata-repository:${project.'cas.version'}" - } -} - -repositories { - # if (project.privateRepoUrl) { - # maven { - # url project.privateRepoUrl - # credentials { - # username = project.privateRepoUsername - # password = System.env.PRIVATE_REPO_TOKEN - # } - # } - # } - # mavenLocal() - # mavenCentral() - # maven { url 'https://oss.sonatype.org/content/repositories/releases' } - maven { - url 'http://maven.aliyun.com/nexus/content/groups/public/' - # url 'https://oss.sonatype.org/content/repositories/snapshots' - # mavenContent { snapshotsOnly() } - } - maven { url 'https://build.shibboleth.net/nexus/content/repositories/releases/' } - maven { - url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' - # url "https://repo.spring.io/milestone" - # mavenContent { releasesOnly() } - } -} - -apply plugin: "io.freefair.war-overlay" -apply plugin: "war" -apply plugin: "org.springframework.boot" -apply plugin: "io.freefair.lombok" - -apply from: rootProject.file("gradle/springboot.gradle") -apply from: rootProject.file("gradle/jib.gradle") -apply from: rootProject.file("gradle/tasks.gradle") - - -configurations.all { - resolutionStrategy { - cacheChangingModulesFor 0, "seconds" - cacheDynamicVersionsFor 0, "seconds" - preferProjectModules() - def failIfConflict = project.hasProperty("failOnVersionConflict") && Boolean.valueOf(project.getProperty("failOnVersionConflict")) - if (failIfConflict) { - failOnVersionConflict() - } - } -} - -war { - entryCompression = ZipEntryCompression.STORED - enabled = false -} - -sourceSets { - bootRunSources { - resources { - srcDirs new File("//etc/cas/templates/"), new File("${project.getProjectDir()}/src/main/resources/") - } - } -} - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(project.targetCompatibility) - } -} - -bootBuildImage { - imageName = "${project.'containerImageOrg'}/${project.'containerImageName'}:${project.version}" -} - -dependencies { - /** - * Do NOT modify the lines below or else you will risk breaking dependency management. - */ - implementation enforcedPlatform("org.apereo.cas:cas-server-support-bom:${project.'cas.version'}") - implementation platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) - - /** - * CAS dependencies and modules may be listed here. - * - * There is no need to specify the version number for each dependency - * since versions are all resolved and controlled by the dependency management - * plugin via the CAS bom. - **/ - - implementation "org.apereo.cas:cas-server-core-api-configuration-model" - implementation "org.apereo.cas:cas-server-webapp-init" - - if (project.hasProperty("casModules")) { - def dependencies = project.getProperty("casModules").split(",") - dependencies.each { - def projectsToAdd = rootProject.subprojects.findAll {project -> - project.name == "cas-server-core-${it}" || project.name == "cas-server-support-${it}" - } - projectsToAdd.each {implementation it} - } - } - - - - - - developmentOnly "org.springframework.boot:spring-boot-devtools:${project.springBootVersion}" -} - diff --git a/apereo-cas/docker-build.sh b/apereo-cas/docker-build.sh deleted file mode 100644 index 24f8764..0000000 --- a/apereo-cas/docker-build.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -imageTag="$1" - -if [ -z "$imageTag" ]; then - version=(`cat gradle.properties | grep "cas.version" | cut -d= -f2`) - imageTag="v$version" -fi - -echo "Building CAS docker image tagged as [$imageTag]" -# read -p "Press [Enter] to continue..." any_key; - -docker build --tag="apereo/cas:$imageTag" . \ - && echo "Built CAS image successfully tagged as apereo/cas:$imageTag" \ - && docker images "apereo/cas:$imageTag" \ No newline at end of file diff --git a/apereo-cas/docker-compose.yml b/apereo-cas/docker-compose.yml deleted file mode 100644 index 8c452cf..0000000 --- a/apereo-cas/docker-compose.yml +++ /dev/null @@ -1,35 +0,0 @@ -version: '3' -# services: -# cas: -# build: . -# ports: -# - "8443:8443" -# - "8080:8080" - -services: - open-oauth2playground: - image: open-oauth2playground:v1.0 - container_name: open-oauth2playground # docker run时指定容器名 - restart: always - ports: - - "8081:88" # docker run时指定端口 - environment: - CAS_SERVER_NAME: "http://cas-demo:8444" # 使用 'cas' 作为主机名 - volumes: - - ./update_cfg.sh:/update_cfg.sh # 路径不对 - - ./cfg.json:/app/Open-OAuth2Playground/cfg.json - command: ["/bin/bash", "-c", "/update_cfg.sh"] - cas-demo: - image: apereo/cas:v6.5.9 - container_name: cas-demo - restart: always - ports: - - "8444:8444" - - "8080:8080" - environment: - CAS_SERVER_NAME: "http://47.100.188.236:8444" # 设置您的 CAS_SERVER_NAME 环境变量 - SERVER_PORT: "8444" # 设置您的 SERVER_PORT 环境变量 - volumes: - - ./cas_init_script.sh:/cas-overlay/cas_init_script.sh - entrypoint: ["/bin/bash", "-c"] - command: ["/cas-overlay/cas_init_script.sh && java -server -noverify -Xmx2048M -jar /cas-overlay/cas.war"] \ No newline at end of file diff --git a/apereo-cas/docker-push.sh b/apereo-cas/docker-push.sh deleted file mode 100644 index 5097107..0000000 --- a/apereo-cas/docker-push.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -docker_user="$1" -docker_psw="$2" -echo "$docker_psw" | docker login --username "$docker_user" --password-stdin - -imageTag="$3" -if [ -z "$imageTag" ]; then - version=(`cat gradle.properties | grep "cas.version" | cut -d= -f2`) - imageTag="v$version" -fi - -echo "Pushing CAS docker image tagged as $imageTag to apereo/cas..." -docker push apereo/cas:"$imageTag" \ - && echo "Pushed apereo/cas:$imageTag successfully."; diff --git a/apereo-cas/docker-run.sh b/apereo-cas/docker-run.sh deleted file mode 100644 index e6fb967..0000000 --- a/apereo-cas/docker-run.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -docker stop cas > /dev/null 2>&1 -docker rm cas > /dev/null 2>&1 -image_tag=(`cat gradle.properties | grep "cas.version" | cut -d= -f2`) -docker run -d -p 8080:8080 -p 8443:8443 --name="cas" apereo/cas:"v${image_tag}" -docker logs -f cas diff --git a/apereo-cas/etc/cas/.ignore b/apereo-cas/etc/cas/.ignore deleted file mode 100644 index e69de29..0000000 diff --git a/apereo-cas/etc/cas/config/cas.properties b/apereo-cas/etc/cas/config/cas.properties deleted file mode 100644 index 33a3de1..0000000 --- a/apereo-cas/etc/cas/config/cas.properties +++ /dev/null @@ -1,52 +0,0 @@ -server.port=${SERVER_PORT:8444} -cas.server.name=http://47.100.188.236:8444 -# cas.server.name=${CAS_SERVER_NAME} -cas.server.prefix=${cas.server.name}/cas - -server.ssl.enabled=false -cas.server.tomcat.http.enabled=true - -logging.config=file:/etc/cas/config/log4j2.xml - -cas.service-registry.core.init-from-json=true -cas.serviceRegistry.json.location=file:/etc/cas/services - -cas.logout.follow-service-redirects=true -cas.logout.redirect-parameter=service - -# CAS Authentication Credentials -# cas.authn.accept.users=casuser::Mellon -cas.authn.accept.enabled=false - -# 添加jdbc相关 -cas.authn.jdbc.query[0].driverClass=org.sqlite.JDBC -cas.authn.jdbc.query[0].url=jdbc:sqlite:/export/data/cas.db -cas.authn.jdbc.query[0].user= -cas.authn.jdbc.query[0].password= -cas.authn.jdbc.query[0].sql=SELECT * FROM user WHERE username=? -cas.authn.jdbc.query[0].fieldPassword=password -cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.SQLiteDialect - -# cas.authn.jdbc.query[0].fieldExpired=expired -# cas.authn.jdbc.query[0].fieldDisabled=disabled -cas.authn.jdbc.query[0].password-encoder.encoding-algorithm= -cas.authn.jdbc.query[0].password-encoder.type=NONE - -cas.authn.attribute-repository.jdbc[0].attributes.name=name -cas.authn.attribute-repository.jdbc[0].attributes.username=username - -# 添加oauth2相关 -cas.authn.oauth.refreshToken.timeToKillInSeconds=2592000 -cas.authn.oauth.code.timeToKillInSeconds=30 -cas.authn.oauth.code.numberOfUses=1 -cas.authn.oauth.accessToken.timeToKillInSeconds=7200 -cas.authn.oauth.accessToken.maxTimeToLiveInSeconds=28800 -cas.authn.oauth.grants.resourceOwner.requireServiceHeader=true - -# 添加密钥相关 -cas.authn.oauth.access-token.crypto.encryption.key=UsvovVy2tnsy9xa0fJbIQVi2q5Kc7KEyNfJ4ygwnFZk -cas.authn.oauth.access-token.crypto.signing.key=xNV2zMA2JF6jCopLxsAO5MIY-Ny71czLHWyg1QnwP1effT_ttubmQyYIglYQPgSGGBFCFli197dtKcuIyG0eMg -cas.tgc.crypto.encryption.key=ZzNLzcVZshSXdRKfwd09UD3ADtP6Q7MFyoDqtDc5y1I -cas.tgc.crypto.signing.key=UBvTb9kDxG-yMxqiXwwEjPP9yy1cxJvIt3GXXcoq7pgWNvJVLCyhU_890jvhauTolIjEEduYSVMntwNKiP4wbg -cas.authn.oauth.crypto.encryption.key=vZb7GPl9iy8pmXFU7aQN1LG70FRkcA-0Y6RRsdgni6M -cas.authn.oauth.crypto.signing.key=Iy9sCAeHXjNMPfHhRvLtU_tSd_j9GD34a7V-SQtXA0z9g12_waZo0TdYutLcosP0xoA-GXYFjiqSNzneaqBD-g diff --git a/apereo-cas/etc/cas/config/log4j2.xml b/apereo-cas/etc/cas/config/log4j2.xml deleted file mode 100644 index 2b6bd47..0000000 --- a/apereo-cas/etc/cas/config/log4j2.xml +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - /var/log - info - warn - info - warn - warn - warn - warn - warn - warn - warn - true - false - - casStackTraceFile - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apereo-cas/etc/cas/services/oauth-3001.json b/apereo-cas/etc/cas/services/oauth-3001.json deleted file mode 100644 index 3cd0806..0000000 --- a/apereo-cas/etc/cas/services/oauth-3001.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "@class" : "org.apereo.cas.support.oauth.services.OAuthRegisteredService", - "clientId": "open-oauth2playground", - "clientSecret": "open-oauth2playground", - "serviceId" : "^(http)://.*", - "name" : "OAuthService", - "id" : 3001, - "attributeReleasePolicy": { - "@class": "org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy", - "allowedAttributes": ["java.util.ArrayList", ["name","username"]] - }, - "generateRefreshToken" : true, - "renewRefreshToken" : true, - "supportedGrantTypes": [ "java.util.HashSet", [ "authorization_code","refresh_token","client_credentials","password" ] ], - "supportedResponseTypes": [ "java.util.HashSet", [ "code","device_code" ] ], - "properties" : { - "@class" : "java.util.HashMap", - "corsAllowCredentials" : { - "@class" : "org.apereo.cas.services.DefaultRegisteredServiceProperty", - "values" : [ "java.util.HashSet", [ "true" ] ] - }, - "corsAllowedOrigins" : { - "@class" : "org.apereo.cas.services.DefaultRegisteredServiceProperty", - "values" : [ "java.util.HashSet", [ "*" ] ] - }, - "corsAllowedMethods" : { - "@class" : "org.apereo.cas.services.DefaultRegisteredServiceProperty", - "values" : [ "java.util.HashSet", [ "POST", "GET", "PUT", "DELETE" ] ] - } - } -} - diff --git a/apereo-cas/etc/cas/services/oauth-pkce-3002.json b/apereo-cas/etc/cas/services/oauth-pkce-3002.json deleted file mode 100644 index bf5fe8f..0000000 --- a/apereo-cas/etc/cas/services/oauth-pkce-3002.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "@class" : "org.apereo.cas.support.oauth.services.OAuthRegisteredService", - "clientId": "open-oauth2playground-pkce", - "serviceId" : "^(http)://.*", - "name" : "OAuthServicePKCE", - "id" : 3002, - "generateRefreshToken" : true, - "renewRefreshToken" : true, - "supportedGrantTypes": [ "java.util.HashSet", [ "authorization_code","refresh_token" ] ], - "supportedResponseTypes": [ "java.util.HashSet", [ "code" ] ] - } \ No newline at end of file diff --git a/apereo-cas/gradle.properties b/apereo-cas/gradle.properties deleted file mode 100644 index 19bc480..0000000 --- a/apereo-cas/gradle.properties +++ /dev/null @@ -1,59 +0,0 @@ - -version=6.5.9 -# CAS server version -cas.version=6.5.9 - -springBootVersion=2.6.3 - -# The version of this overlay project -group=org.apereo.cas -artifactId=cas-overlay -sourceCompatibility=11 -targetCompatibility=11 - -gradleFreeFairPluginVersion=6.5.1 -gradleDependencyManagementPluginVersion=1.1.0 - -# Used to build docker images -jibVersion=3.3.1 - -# Specify the coordinates of the container image to build via jib -containerImageOrg=apereo -containerImageName=cas - -baseDockerImage=eclipse-temurin:11-jdk -allowInsecureRegistries=false -dockerImagePlatform=amd64:linux - -containerImage=apereo/cas - -# Include launch script for executable WAR artifact -# Setting this to true allows the final web application -# to be fully executable on its own -executable=false - -# Use -tomcat, -jetty, -undertow for deployment to other embedded containers -# if the overlay application supports or provides the chosen type. -# You should set this to blank if you want to deploy to an external container. -# and want to set up, download and manage the container (i.e. Apache Tomcat) yourself. -appServer=-tomcat - -# Settings to generate keystore -# used by the build to assist with creating -# self-signed certificates for https endpoints -certDir=/etc/cas -serverKeystore=thekeystore -exportedServerCert=cas.crt -storeType=PKCS12 - -# Location of the downloaded CAS Shell JAR -shellDir=build/libs -ivyVersion=2.5.0 -gradleDownloadTaskVersion=4.1.1 - -tomcatVersion=9.0.69 - -# Include private repository -# override these in user properties or pass in values from env on command line -privateRepoUrl= -privateRepoUsername= diff --git a/apereo-cas/gradle/jib.gradle b/apereo-cas/gradle/jib.gradle deleted file mode 100644 index 60e99fb..0000000 --- a/apereo-cas/gradle/jib.gradle +++ /dev/null @@ -1,63 +0,0 @@ -apply plugin: "com.google.cloud.tools.jib" - -def imagePlatforms = project.dockerImagePlatform.split(",") -def dockerUsername = providers.systemProperty("dockerUsername").getOrNull() -def dockerPassword = providers.systemProperty("dockerPassword").getOrNull() - -jib { - from { - image = project.baseDockerImage - platforms { - imagePlatforms.each { - def given = it.split(":") - platform { - architecture = given[0] - os = given[1] - } - } - } - } - to { - image = "${project.containerImage}:${project.version}" - /** - ecr-login: Amazon Elastic Container Registry (ECR) - gcr: Google Container Registry (GCR) - osxkeychain: Docker Hub - */ - credHelper = "osxkeychain" - if (dockerUsername != null && dockerPassword != null) { - auth { - username = "${dockerUsername}" - password = "${dockerPassword}" - } - } - tags = [project.version] - } - container { - creationTime = "USE_CURRENT_TIMESTAMP" - entrypoint = ['/docker/entrypoint.sh'] - ports = ['80', '443', '8080', '8443', '8444', '8761', '8888', '5000'] - labels = [version:project.version, name:project.name, group:project.group, org:project.containerImageOrg] - workingDirectory = '/docker/cas/war' - } - extraDirectories { - paths { - path { - from = file('src/main/jib') - } - path { - from = file('etc/cas') - into = '/etc/cas' - } - path { - from = file("build/libs") - into = "/docker/cas/war" - } - } - permissions = [ - '/docker/entrypoint.sh': '755' - ] - } - allowInsecureRegistries = project.allowInsecureRegistries -} - diff --git a/apereo-cas/gradle/springboot.gradle b/apereo-cas/gradle/springboot.gradle deleted file mode 100644 index a60ab7f..0000000 --- a/apereo-cas/gradle/springboot.gradle +++ /dev/null @@ -1,136 +0,0 @@ -apply plugin: "org.springframework.boot" - -repositories { - maven { - url "https://repo.spring.io/milestone" - } - mavenLocal() - mavenCentral() - maven { url 'https://oss.sonatype.org/content/repositories/releases' } - maven { - url 'https://oss.sonatype.org/content/repositories/snapshots' - mavenContent { snapshotsOnly() } - } - maven { url 'https://build.shibboleth.net/nexus/content/repositories/releases/' } -} - -configurations { - bootRunConfig.extendsFrom compileClasspath -} - -dependencies { - bootRunConfig "org.apereo.cas:cas-server-core:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-core-logging:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-core-web:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-core-webflow:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-core-cookie:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-core-logout:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-core-authentication:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-core-validation:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-core-audit:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-core-tickets:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-core-services:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-core-util:${project.'cas.version'}" - - bootRunConfig "org.apereo.cas:cas-server-support-thymeleaf:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-support-validation:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-support-person-directory:${project.'cas.version'}" - - bootRunConfig "org.apereo.cas:cas-server-webapp-resources:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-webapp-config:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-webapp-init:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-webapp-tomcat:${project.'cas.version'}" - bootRunConfig "org.apereo.cas:cas-server-webapp-init-tomcat:${project.'cas.version'}" - - bootRunConfig "org.springframework.boot:spring-boot-devtools:${project.springBootVersion}" -} - -bootRun { - classpath = configurations.bootRunConfig + sourceSets.main.compileClasspath + sourceSets.main.runtimeClasspath - doFirst { - sourceResources sourceSets.bootRunSources - systemProperties = System.properties - } - - def list = [] - list.add("-XX:TieredStopAtLevel=1") - list.add("-Xverify:none") - list.add("--add-modules") - list.add("java.se") - list.add("--add-exports") - list.add("java.base/jdk.internal.ref=ALL-UNNAMED") - list.add("--add-opens") - list.add("java.base/java.lang=ALL-UNNAMED") - list.add("--add-opens") - list.add("java.base/java.nio=ALL-UNNAMED") - list.add("--add-opens") - list.add("java.base/sun.nio.ch=ALL-UNNAMED") - list.add("--add-opens") - list.add("java.management/sun.management=ALL-UNNAMED") - list.add("--add-opens") - list.add("jdk.management/com.sun.management.internal=ALL-UNNAMED") - list.add("-Xrunjdwp:transport=dt_socket,address=5000,server=y,suspend=n") - - jvmArgs = list - - def appArgList = [] - args = appArgList -} - -springBoot { - buildInfo() - mainClass = "org.apereo.cas.web.CasWebApplication" - - - - -} - -bootWar { - def executable = project.hasProperty("executable") && Boolean.valueOf(project.getProperty("executable")) - if (executable) { - logger.info "Including launch script for executable WAR artifact" - launchScript() - } else { - logger.info "WAR artifact is not marked as an executable" - } - - archiveFileName = "cas.war" - archiveBaseName = "cas" - - entryCompression = ZipEntryCompression.STORED - - /* - attachClasses = true - classesClassifier = 'classes' - archiveClasses = true - */ - - overlays { - /* - https://docs.freefair.io/gradle-plugins/current/reference/#_io_freefair_war_overlay - Note: The "excludes" property is only for files in the war dependency. - If a jar is excluded from the war, it could be brought back into the final war as a dependency - of non-war dependencies. Those should be excluded via normal gradle dependency exclusions. - */ - cas { - from "org.apereo.cas:cas-server-webapp${project.appServer}:${project.'cas.version'}@war" - - - - - - provided = false - excludes = ["WEB-INF/lib/servlet-api-2*.jar"] - - /* - excludes = ["WEB-INF/lib/somejar-1.0*"] - enableCompilation = true - includes = ["*.xyz"] - targetPath = "sub-path/bar" - skip = false - */ - } - } -} - diff --git a/apereo-cas/gradle/tasks.gradle b/apereo-cas/gradle/tasks.gradle deleted file mode 100644 index 7e56e61..0000000 --- a/apereo-cas/gradle/tasks.gradle +++ /dev/null @@ -1,420 +0,0 @@ -import org.apereo.cas.metadata.* -import java.nio.file.* -buildscript { - repositories { - mavenLocal() - mavenCentral() - gradlePluginPortal() - maven { - url 'https://oss.sonatype.org/content/repositories/snapshots' - mavenContent { snapshotsOnly() } - } - maven { - url "https://repo.spring.io/milestone" - mavenContent { releasesOnly() } - } - } - dependencies { - classpath "org.apache.ivy:ivy:${project.ivyVersion}" - classpath "org.apereo.cas:cas-server-core-configuration-metadata-repository:${project.'cas.version'}" - } -} -apply plugin: "de.undercouch.download" - -task run(group: "build", description: "Run the CAS web application in embedded container mode") { - dependsOn 'build' - doLast { - def casRunArgs = Arrays.asList("-server -noverify -Xmx2048M -XX:+TieredCompilation -XX:TieredStopAtLevel=1".split(" ")) - javaexec { - main = "-jar" - jvmArgs = casRunArgs - args = ["build/libs/cas.war"] - systemProperties = System.properties - logger.info "Started ${commandLine}" - } - } -} - -task setExecutable(group: "CAS", description: "Configure the project to run in executable mode") { - doFirst { - project.setProperty("executable", "true") - logger.info "Configuring the project as executable" - } -} - -task executable(type: Exec, group: "CAS", description: "Run the CAS web application in standalone executable mode") { - dependsOn setExecutable, 'build' - doFirst { - workingDir "." - if (!Os.isFamily(Os.FAMILY_WINDOWS)) { - commandLine "chmod", "+x", bootWar.archivePath - } - logger.info "Running ${bootWar.archivePath}" - commandLine bootWar.archivePath - } -} - - -task debug(group: "CAS", description: "Debug the CAS web application in embedded mode on port 5005") { - dependsOn 'build' - doLast { - logger.info "Debugging process is started in a suspended state, listening on port 5005." - def casArgs = Arrays.asList("-Xmx2048M".split(" ")) - javaexec { - main = "-jar" - jvmArgs = casArgs - debug = true - args = ["build/libs/cas.war"] - systemProperties = System.properties - logger.info "Started ${commandLine}" - } - } -} - -task showConfiguration(group: "CAS", description: "Show configurations for each dependency, etc") { - doLast() { - def cfg = project.hasProperty("configuration") ? project.property("configuration") : "compile" - configurations.getByName(cfg).each { println it } - } -} - -task allDependenciesInsight(group: "build", type: DependencyInsightReportTask, description: "Produce insight information for all dependencies") {} - -task allDependencies(group: "build", type: DependencyReportTask, description: "Display a graph of all project dependencies") {} - -task casVersion(group: "CAS", description: "Display the current CAS version") { - doFirst { - def verbose = project.hasProperty("verbose") && Boolean.valueOf(project.getProperty("verbose")) - if (verbose) { - def out = services.get(StyledTextOutputFactory).create("CAS") - println "******************************************************************" - out.withStyle(Style.Info).println "Apereo CAS ${project.version}" - out.withStyle(Style.Description).println "Enterprise Single SignOn for all earthlings and beyond" - out.withStyle(Style.SuccessHeader).println "- GitHub: " - out.withStyle(Style.Success).println "https://github.com/apereo/cas" - out.withStyle(Style.SuccessHeader).println "- Docs: " - out.withStyle(Style.Success).println "https://apereo.github.io/cas" - out.withStyle(Style.SuccessHeader).println "- Blog: " - out.withStyle(Style.Success).println "https://apereo.github.io" - println "******************************************************************" - } else { - println project.version - } - } -} - -task springBootVersion(description: "Display current Spring Boot version") { - doLast { - println rootProject.springBootVersion - } -} - -task containerImage(description: "Display container image name") { - doLast { - println rootProject.containerImage - } -} - -task zip(type: Zip) { - from projectDir - exclude '**/.idea/**', '.gradle', 'tmp', '.git', '**/build/**', '**/bin/**', '**/out/**', '**/.settings/**' - destinationDirectory = buildDir - archiveFileName = "${project.name}.zip" - doLast { - def zipFile = file("${buildDir}/${it.archiveFileName.get()}") - if (zipFile.exists()) { - println "Zip archive is available at ${zipFile.absolutePath}" - } - } -} - -task createKeystore(group: "CAS", description: "Create CAS keystore") { - doFirst { - def certDir = project.getProperty("certDir") - def serverKeyStore = project.getProperty("serverKeystore") - def exportedServerCert = project.getProperty("exportedServerCert") - def storeType = project.getProperty("storeType") - def keystorePath = "$certDir/$serverKeyStore" - def serverCert = "$certDir/$exportedServerCert" - - mkdir certDir - - def dn = "CN=cas.example.org,OU=Example,OU=Org,C=US" - if (project.hasProperty("certificateDn")) { - dn = project.getProperty("certificateDn") - } - def subjectAltName = "dns:example.org,dns:localhost,ip:127.0.0.1" - if (project.hasProperty("certificateSubAltName")) { - subjectAltName = project.getProperty("certificateSubAltName") - } - // this will fail if thekeystore exists and has cert with cas alias already (so delete if you want to recreate) - logger.info "Generating keystore for CAS with DN ${dn}" - exec { - workingDir "." - commandLine "keytool", "-genkeypair", "-alias", "cas", - "-keyalg", "RSA", - "-keypass", "changeit", "-storepass", "changeit", - "-keystore", keystorePath, - "-dname", dn, "-ext", "SAN=${subjectAltName}", - "-storetype", storeType - } - logger.info "Exporting cert from keystore..." - exec { - workingDir "." - commandLine "keytool", "-exportcert", "-alias", "cas", - "-storepass", "changeit", "-keystore", keystorePath, - "-file", serverCert - } - logger.info "Import $serverCert into your Java truststore (\$JAVA_HOME/lib/security/cacerts)" - } -} - -task unzipWAR(type: Copy, group: "CAS", description: "Explodes the CAS web application archive") { - dependsOn 'build' - from zipTree("build/libs/cas.war") - into "${buildDir}/app" - doLast { - println "Unzipped WAR into ${buildDir}/app" - } -} - -task verifyRequiredJavaVersion { - def currentVersion = org.gradle.api.JavaVersion.current() - logger.info "Checking current Java version ${currentVersion} for required Java version ${project.targetCompatibility}" - if (!currentVersion.name.equalsIgnoreCase("${project.targetCompatibility}")) { - logger.warn("Careful: Current Java version ${currentVersion} does not match required Java version ${project.targetCompatibility}") - } -} - -task copyCasConfiguration(type: Copy, group: "CAS", - description: "Copy the CAS configuration from this project to /etc/cas/config") { - from "etc/cas/config" - into new File('/etc/cas/config').absolutePath - doFirst { - new File('/etc/cas/config').mkdirs() - } -} - - -def tomcatDirectory = "${buildDir}/apache-tomcat-${tomcatVersion}" -project.ext."tomcatDirectory" = tomcatDirectory - -def explodedDir = "${buildDir}/app" -def explodedResourcesDir = "${buildDir}/cas-resources" - -def resourcesJarName = "cas-server-webapp-resources" -def templateViewsJarName = "cas-server-support-thymeleaf" - -task unzip(type: Copy, group: "CAS", description: "Explodes the CAS archive and resources jar from the CAS web application archive") { - dependsOn unzipWAR - from zipTree("${explodedDir}/WEB-INF/lib/${templateViewsJarName}-${project.'cas.version'}.jar") - into explodedResourcesDir - - from zipTree("${explodedDir}/WEB-INF/lib/${resourcesJarName}-${project.'cas.version'}.jar") - into explodedResourcesDir - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - doLast { - println "Exploded WAR resources into ${explodedResourcesDir}" - } -} - -task downloadShell(group: "Shell", description: "Download CAS shell jar from snapshot or release maven repo") { - doFirst { - mkdir "${project.shellDir}" - } - doLast { - def downloadFile - if (isRunningCasServerSnapshot()) { - def snapshotDir = "https://oss.sonatype.org/content/repositories/snapshots/org/apereo/cas/cas-server-support-shell/${project.'cas.version'}/" - def files = new org.apache.ivy.util.url.ApacheURLLister().listFiles(new URL(snapshotDir)) - files = files.sort { it.path } - files.each { - if (it.path.endsWith(".jar")) { - downloadFile = it - } - } - } else { - downloadFile = "https://repo1.maven.org/maven2/org/apereo/cas/cas-server-support-shell/${project.'cas.version'}/cas-server-support-shell-${project.'cas.version'}.jar" - } - logger.info "Downloading file: ${downloadFile}" - download { - src downloadFile - dest new File("${project.shellDir}", "cas-server-support-shell-${project.'cas.version'}.jar") - overwrite false - } - } -} - -task runShell(group: "Shell", description: "Run the CAS shell") { - dependsOn downloadShell - doLast { - println "Run the following command to launch the shell:\n\tjava -jar ${project.shellDir}/cas-server-support-shell-${project.'cas.version'}.jar" - } -} - -task debugShell(group: "Shell", description: "Run the CAS shell with debug options, wait for debugger on port 5005") { - dependsOn downloadShell - doLast { - println """ - Run the following command to launch the shell:\n\t - java -Xrunjdwp:transport=dt_socket,address=5000,server=y,suspend=y -jar ${project.shellDir}/cas-server-support-shell-${project.'cas.version'}.jar - """ - } -} - -task listTemplateViews(group: "CAS", description: "List all CAS views") { - dependsOn unzip - - doFirst { - fileTree(explodedResourcesDir).matching { - include "**/*.html" - } - .collect { - return it.path.replace(explodedResourcesDir, "") - } - .toSorted() - .each { println it } - } -} - -task getResource(group: "CAS", description: "Fetch a CAS resource and move it into the overlay") { - dependsOn unzip - - doFirst { - def resourceName = project.getProperty("resourceName") - - def results = fileTree(explodedResourcesDir).matching { - include "**/${resourceName}.*" - include "**/${resourceName}" - } - if (results.isEmpty()) { - println "No resources could be found matching ${resourceName}" - return - } - if (results.size() > 1) { - println "Multiple resources found matching ${resourceName}:\n" - results.each { - println "\t-" + it.path.replace(explodedResourcesDir, "") - } - println "\nNarrow down your search criteria and try again." - return - } - - def fromFile = explodedResourcesDir - def resourcesDir = "src/main/resources" - mkdir resourcesDir - - def resourceFile = results[0].canonicalPath - def toResourceFile = resourceFile.replace(fromFile, resourcesDir) - - def parent = file(toResourceFile).getParent() - mkdir parent - - Files.copy(Paths.get(resourceFile), Paths.get(toResourceFile), StandardCopyOption.REPLACE_EXISTING) - println "Copied file ${resourceFile} to ${toResourceFile}" - } -} - -def isRunningCasServerSnapshot() { - return "${project.'cas.version'}".contains("-SNAPSHOT") -} - - -task createTheme(group: "CAS", description: "Create theme directory structure in the overlay") { - doFirst { - def theme = project.getProperty("theme") - def builder = new FileTreeBuilder() - new File("src/main/resources/${theme}.properties").delete() - - builder.src { - main { - resources { - "static" { - themes { - "${theme}" { - css { - 'cas.css'('') - } - js { - 'cas.js'('') - } - images { - '.ignore'('') - } - } - } - } - - templates { - "${theme}" { - fragments { - - } - } - } - - "${theme}.properties"("""cas.standard.css.file=/themes/${theme}/css/cas.css -cas.standard.js.file=/themes/${theme}/js/cas.js - """) - } - } - } - } -} - - -def skipValidation = project.hasProperty("validate") && project.property("validate").equals("false") -if (!skipValidation) { - task validateConfiguration(type: Copy, group: "CAS", - description: "Validate CAS configuration") { - def file = new File("${projectDir}/src/main/resources/application.properties") - if (file.exists()) { - throw new GradleException("This overlay project is overriding a CAS-supplied configuration file at ${file.path}. " - + "Overriding this file will disable all default CAS settings that are provided to the overlay, and " - + "generally has unintended side-effects. It's best to move your configuration inside an application.yml " - + "file, if you intend to keep the configuration bundled with the CAS web application. \n\nTo disable this " - + "validation step, run the build with -Pvalidate=false."); - } - } - processResources.dependsOn(validateConfiguration) -} - - -task exportConfigMetadata(group: "CAS", description: "Export collection of CAS properties") { - doLast { - def file = new File(project.rootDir, 'config-metadata.properties') - def queryType = ConfigurationMetadataCatalogQuery.QueryTypes.CAS - if (project.hasProperty("queryType")) { - queryType = ConfigurationMetadataCatalogQuery.QueryTypes.valueOf(project.findProperty("queryType")) - } - file.withWriter('utf-8') { writer -> - def props = CasConfigurationMetadataCatalog.query( - ConfigurationMetadataCatalogQuery.builder() - .queryType(queryType) - .build()) - .properties() - props.each { property -> - writer.writeLine("# Type: ${property.type}"); - writer.writeLine("# Module: ${property.module}") - writer.writeLine("# Owner: ${property.owner}") - if (property.deprecationLevel != null) { - writer.writeLine("# This setting is deprecated with a severity level of ${property.deprecationLevel}.") - if (property.deprecationReason != null) { - writer.writeLine("# because ${property.deprecationReason}") - } - if (property.deprecationReason != null) { - writer.writeLine("# Replace with: ${property.deprecationReason}") - } - } - writer.writeLine("#") - def description = property.description.replace("\n", "\n# ").replace("\r", "") - description = org.apache.commons.text.WordUtils.wrap(description, 70, "\n# ", true) - writer.writeLine("# ${description}") - writer.writeLine("#") - writer.writeLine("# ${property.name}: ${property.defaultValue}") - writer.writeLine("") - } - } - println "Configuration metadata is available at ${file.absolutePath}" - } -} diff --git a/apereo-cas/gradle/wrapper/gradle-wrapper.jar b/apereo-cas/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61574 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 ScNgau(U?ep-K_E5zy1%ZQTdPn diff --git a/apereo-cas/gradle/wrapper/gradle-wrapper.properties b/apereo-cas/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index d9ebbe2..0000000 --- a/apereo-cas/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=gradle-7.6-bin.zip -networkTimeout=10000 -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/apereo-cas/gradlew b/apereo-cas/gradlew deleted file mode 100644 index 65dcd68..0000000 --- a/apereo-cas/gradlew +++ /dev/null @@ -1,244 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/apereo-cas/gradlew.bat b/apereo-cas/gradlew.bat deleted file mode 100644 index 93e3f59..0000000 --- a/apereo-cas/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/apereo-cas/helm/README.md b/apereo-cas/helm/README.md deleted file mode 100644 index 12b4153..0000000 --- a/apereo-cas/helm/README.md +++ /dev/null @@ -1,114 +0,0 @@ -## Helm Chart for CAS - -The current helm chart for cas-server demonstrates standing up CAS with a Spring Boot Admin Server. -The chart functionality will grow over time, hopefully with contributions from real world deployments. -Eventually it might be nice to support a config-server and have cas-management available. -The chart supports mapping in arbitrary volumes and cas config can be specified in values files. -The config could be in cloud config rather than kubernetes config maps, the service registry -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, -postgresql, or mongo, etc. -Currently the chart is attempting to use SSL between ingress controller and the CAS and Boot Admin servers. -This is probably overkill and involves all the pain that comes with SSL (e.g. trust & hostname verification). -This chart uses stateful set for CAS rather than a deployment and this may change in the future. -The bootadmin CAS server discovery method should probably change to "cloud" method eventually. - -#### Warning: semver versioning will not be employed until published to a repository. - -### Install Kubernetes (Docker for Windows/Mac, Minikube, K3S, Rancher, etc) - - - [Docker Desktop](https://www.docker.com/products/docker-desktop) - - - [Minikube](https://minikube.sigs.k8s.io/docs/start/) - - - [k3s](https://k3s.io/) - Works on linux, very light-weight and easy to install for development - ```shell script - curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --disable traefik" sh - # the following export is for helm - export KUBECONFIG=/etc/rancher/k3s/k3s.yaml - ./gradlew clean build jibBuildTar --refresh-dependencies - k3s ctr images import build/jib-image.tar - k3s ctr images ls | grep cas - ./gradlew createKeystore - cd helm - # create secret for tomcat - kubectl create secret generic cas-server-keystore --from-file=thekeystore=/etc/cas/thekeystore - # create secret for ingress controller to use with CAS ingress (nginx-ingress will use default if you don't create) - ./create-ingress-tls.sh - # install cas-server helm chart - helm upgrade --install cas-server ./cas-server - ``` - -### Install Helm and Kubectl - -Helm v3 and Kubectl are just single binary programs. Kubectl may come with your kubernetes -installation, but you can download both of programs and put them in your path. - - Install [Helm](https://helm.sh/docs/intro/install/) - - Install [Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) - -### Install ingress controller - -CAS helm chart only tested with Kubernetes ingress-nginx, feel free to add support for other ingress controllers. - -[Kubernetes Nginx Ingress Installation Guide](https://kubernetes.github.io/ingress-nginx/deploy/) - -### Create secret containing keystore - -Assuming you have run `./gradlew createKeystore` or put you server keystore in `/etc/cas/thekeystore`, -run the following to create a secret containing the keystore: -```shell script -kubectl create secret generic cas-server-keystore --from-file=thekeystore=/etc/cas/thekeystore -``` - -### Install CAS Server helm chart - -Helm charts consist of templates which are combined with values from one or more values files -(and command line set arguments) to produce kubernetes yaml. The templates folder contains a default -values.yaml that is used by default but additional values files can be specified on the command line. -The following examples use the `default` namespace but `--namespace cas` can be added to any resources -created by the helm command to use the specified kubernetes namespace. -``` -# delete cas-server helm chart install -helm delete cas-server -# install cas-server chart -helm install cas-server ./cas-server -# install or update cas-server -helm upgrade --install cas-server ./cas-server -# use local values file to override defaults -helm upgrade --install cas-server --values values-local.yaml ./cas-server -# see kubernetes yaml without installing -helm upgrade --install cas-server --values values-local.yaml ./cas-server --dry-run --debug -# sometimes dry-run fails b/c yaml can't convert to json so use template instead to see problem -helm template cas-server --values values-local.yaml ./cas-server --debug -``` - -### Useful `kubectl` Commands - -``` -# tail the console logs -kubectl logs cas-server-0 -f -# exec into container -kubectl exec -it cas-server-0 sh -# bounce CAS pod -kubectl delete pod cas-server-0 -``` - -### Browse to CAS - -Make sure you have host entries for whatever host is listed in values file for this entry: -``` -ingress: - hosts: - - host: cas.example.org - paths: - - "/cas" - tls: - - secretName: cas-server-ingress-tls - hosts: - - cas.example.org -``` - -``` -# host entry -127.0.0.1 cas.example.org -``` -Browse to `https://cas.example.org/cas/login` diff --git a/apereo-cas/helm/cas-server/.helmignore b/apereo-cas/helm/cas-server/.helmignore deleted file mode 100644 index 0e8a0eb..0000000 --- a/apereo-cas/helm/cas-server/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/apereo-cas/helm/cas-server/Chart.yaml b/apereo-cas/helm/cas-server/Chart.yaml deleted file mode 100644 index c999c35..0000000 --- a/apereo-cas/helm/cas-server/Chart.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v2 -name: cas-server -description: A Helm chart for CAS SSO Server -icon: "https://apereo.github.io/cas/images/cas_logo.png" - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 'latest' diff --git a/apereo-cas/helm/cas-server/templates/NOTES.txt b/apereo-cas/helm/cas-server/templates/NOTES.txt deleted file mode 100644 index b97ad96..0000000 --- a/apereo-cas/helm/cas-server/templates/NOTES.txt +++ /dev/null @@ -1,31 +0,0 @@ -1. Get the application URL by running these commands: -{{- if .Values.cas.ingress.enabled }} -{{- range $host := .Values.cas.ingress.hosts }} - {{- range .paths }} - curl -k -v http{{ if $.Values.cas.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}/login - {{- end }} -{{- end }} -{{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "cas-server.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT -{{- else if contains "LoadBalancer" .Values.service.type }} - NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "cas-server.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "cas-server.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") - echo http://$SERVICE_IP:{{ .Values.service.port }} -{{- else if contains "ClusterIP" .Values.service.type }} - 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}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80 -{{- end }} - -{{- if .Values.bootadmin.ingress.enabled }} -Access boot admin UI via the following URLs: -{{- range $host := .Values.bootadmin.ingress.hosts }} - {{- range .paths }} - curl -k -v http{{ if $.Values.bootadmin.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} - {{- end }} -{{- end }} -{{- end }} -Kubernetes Version: {{ .Capabilities.KubeVersion.Version }} diff --git a/apereo-cas/helm/cas-server/templates/_helpers.tpl b/apereo-cas/helm/cas-server/templates/_helpers.tpl deleted file mode 100644 index 9d84280..0000000 --- a/apereo-cas/helm/cas-server/templates/_helpers.tpl +++ /dev/null @@ -1,187 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "cas-server.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "cas-server.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create a name for boot admin deployment -*/}} -{{- define "cas-server.bootadminname" -}} -{{- $bootadminsuffix := default "boot-admin" .Values.bootadminSuffixOverride }} -{{- printf "%s-%s" (include "cas-server.fullname" . | trunc 43 | trimSuffix "-") $bootadminsuffix }} -{{- end }} - -{{/* -Create a name for cas mgmt deployment -*/}} -{{- define "cas-server.mgmtname" -}} -{{- $mgmtsuffix := default "mgmt" .Values.mgmtSuffixOverride }} -{{- printf "%s-%s" (include "cas-server.fullname" . | trunc 43 | trimSuffix "-") $mgmtsuffix }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "cas-server.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "cas-server.labels" -}} -helm.sh/chart: {{ include "cas-server.chart" . }} -{{ include "cas-server.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "cas-server.selectorLabels" -}} -app.kubernetes.io/name: {{ include "cas-server.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Bootadmin Selector labels -*/}} -{{- define "cas-bootadmin.selectorLabels" -}} -app.kubernetes.io/name: {{ include "cas-server.bootadminname" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Bootadmin Pod labels -*/}} -{{- define "cas-bootadmin.labels" -}} -cas.server-type: bootadmin -{{- end }} - -{{/* -CAS Mgmt Selector labels -*/}} -{{- define "cas-mgmt.selectorLabels" -}} -app.kubernetes.io/name: {{ include "cas-server.mgmtname" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -CAS Mgmt Pod labels -*/}} -{{- define "cas-mgmt.labels" -}} -cas.server-type: mgmt -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "cas-server.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "cas-server.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} - -{{/* -Return the proper cas-server image name -*/}} -{{- define "cas-server.imageName" -}} -{{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} -{{- end -}} - -{{/* -Return the proper cas-server boot admin image name -*/}} -{{- define "cas-server.bootadminImageName" -}} -{{ include "common.images.image" (dict "imageRoot" .Values.bootadminimage "global" .Values.global) }} -{{- end -}} - -{{/* -Return the proper CAS management image name -*/}} -{{- define "cas-server.mgmtImageName" -}} -{{ include "common.images.image" (dict "imageRoot" .Values.mgmtimage "global" .Values.global) }} -{{- end -}} - -{{/* -Return the proper image name (for the init container volume-permissions image) -*/}} -{{- define "cas-server.volumePermissions.image" -}} -{{ include "common.images.image" (dict "imageRoot" .Values.volumePermissions.image "global" .Values.global) }} -{{- end -}} - -{{/* -Return the proper image name -{{ include "common.images.image" ( dict "imageRoot" .Values.path.to.the.image "global" $) }} -*/}} -{{- define "common.images.image" -}} -{{- $registryName := .imageRoot.registry -}} -{{- $repositoryName := .imageRoot.repository -}} -{{- $tag := default "latest" .imageRoot.tag | toString -}} -{{- if .global }} - {{- if .global.imageRegistry }} - {{- $registryName = .global.imageRegistry -}} - {{- end -}} -{{- end -}} -{{- if ne $registryName "" }} - {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- else -}} - {{- printf "%s:%s" $repositoryName $tag -}} -{{- end -}} -{{- end -}} - - -{{/* -Return log directory volume -*/}} -{{- define "cas-server.logdir" -}} -{{- if .Values.logdir.hostPath -}} -hostPath: - path: {{ .Values.logdir.hostPath }} - type: Directory -{{- else if .Values.logdir.claimName -}} -persistentVolumeClaim: - claimName: {{ .Values.logdir.claimName }} -{{- else -}} -emptyDir: {} -{{- end }} -{{- end -}} - - -{{/* -Renders a value that contains template. -Usage: -{{ include "cas-server.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} -*/}} -{{- define "cas-server.tplvalues.render" -}} - {{- if typeIs "string" .value }} - {{- tpl .value .context }} - {{- else }} - {{- tpl (.value | toYaml) .context }} - {{- end }} -{{- end -}} diff --git a/apereo-cas/helm/cas-server/templates/bootadmin/configmap.yaml b/apereo-cas/helm/cas-server/templates/bootadmin/configmap.yaml deleted file mode 100644 index f575636..0000000 --- a/apereo-cas/helm/cas-server/templates/bootadmin/configmap.yaml +++ /dev/null @@ -1,9 +0,0 @@ -{{- if .Values.bootadmin.enabled -}} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "cas-server.bootadminname" . }}-config - labels: {{- include "cas-server.labels" . | nindent 4 }} -data: - {{- include "cas-server.tplvalues.render" (dict "value" .Values.bootAdminContainer.casConfig "context" $) | nindent 2 }} -{{- end }} diff --git a/apereo-cas/helm/cas-server/templates/bootadmin/deployment.yaml b/apereo-cas/helm/cas-server/templates/bootadmin/deployment.yaml deleted file mode 100644 index 30e5eb4..0000000 --- a/apereo-cas/helm/cas-server/templates/bootadmin/deployment.yaml +++ /dev/null @@ -1,188 +0,0 @@ -{{- if .Values.bootadmin.enabled -}} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "cas-server.bootadminname" . }} - labels: {{- include "cas-server.labels" . | nindent 4 }} -spec: - replicas: {{ .Values.bootadmin.replicaCount }} - selector: - matchLabels: - {{- include "cas-bootadmin.selectorLabels" . | nindent 6 }} - template: - metadata: - annotations: - {{- with .Values.podAnnotations }} - {{- toYaml . | nindent 8 }} - {{- end }} - {{ if .Values.bootAdminContainer.alwaysRoll }} - rollme: {{ randAlphaNum 5 | quote }} - {{- else }} - rollme: "rolldisabled" - {{- end }} - labels: - {{- include "cas-bootadmin.selectorLabels" . | nindent 8 }} - {{- include "cas-bootadmin.labels" . | nindent 8 }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "cas-server.serviceAccountName" . }} - {{- if .Values.podSecurityContext.enabled }} - securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }} - {{- end }} - volumes: - {{- range $.Values.bootAdminContainer.casConfigMounts }} - {{- $configMount := printf "%s-%s" "bootadmin-config" . | replace "." "-" | replace "_" "-" | lower }} - - name: {{ $configMount | quote }} - configMap: - name: {{ include "cas-server.bootadminname" $ }}-config - defaultMode: 0444 - {{- end }} - - name: scripts - configMap: - name: {{ include "cas-server.fullname" . }}-scripts - defaultMode: 0555 - {{- if .Values.bootAdminContainer.serverKeystoreExistingSecret }} - - name: cas-server-keystore - secret: - secretName: {{ .Values.bootAdminContainer.serverKeystoreExistingSecret }} - defaultMode: 0444 - items: - - key: {{ .Values.bootAdminContainer.serverKeystoreSubPath }} - path: {{ .Values.bootAdminContainer.serverKeystoreSubPath }} - {{- end }} - {{- if .Values.bootAdminContainer.extraVolumes }} - {{- include "cas-server.tplvalues.render" ( dict "value" .Values.bootAdminContainer.extraVolumes "context" $ ) | nindent 10 }} - {{- end }} - containers: - - name: cas-boot-admin - {{- if .Values.containerSecurityContext.enabled }} - securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} - {{- end }} - image: {{ include "cas-server.bootadminImageName" . }} - imagePullPolicy: {{ .Values.bootadminimage.pullPolicy }} - env: - {{- if .Values.bootAdminContainer.warPath }} - - name: CAS_BOOTADMIN_WAR - value: {{ .Values.bootAdminContainer.warPath | quote }} - {{- end }} - {{- if .Values.bootAdminContainer.profiles }} - - name: CAS_SPRING_PROFILES - value: {{ .Values.bootAdminContainer.profiles | quote }} - {{- end }} - {{- if .Values.bootAdminContainer.jvm.maxHeapOpt }} - - name: MAX_HEAP_OPT - value: {{ .Values.bootAdminContainer.jvm.maxHeapOpt | quote }} - {{- end }} - {{- if .Values.bootAdminContainer.jvm.minHeapOpt }} - - name: MIN_HEAP_OPT - value: {{ .Values.bootAdminContainer.jvm.minHeapOpt | quote }} - {{- end }} - {{- if .Values.bootAdminContainer.jvm.extraOpts }} - - name: JVM_EXTRA_OPTS - value: {{ .Values.bootAdminContainer.jvm.extraOpts | quote }} - {{- end }} - - name: JAVA_ENABLE_DEBUG - value: {{ .Values.bootAdminContainer.jvm.debugEnabled | quote }} - - name: JAVA_DEBUG_SUSPEND - value: {{ .Values.bootAdminContainer.jvm.debugSuspend | quote }} - - name: 'POD_IP' - valueFrom: - fieldRef: - fieldPath: status.podIP - {{- if .Values.bootAdminContainer.extraEnvVars }} - {{- include "cas-server.tplvalues.render" (dict "value" .Values.bootAdminContainer.extraEnvVars "context" $) | nindent 12 }} - {{- end }} - envFrom: - {{- if .Values.bootAdminContainer.extraEnvVarsConfigMap }} - - configMapRef: - name: {{ .Values.bootAdminContainer.extraEnvVarsConfigMap }} - {{- end }} - {{- if .Values.bootAdminContainer.extraEnvVarsSecret }} - - secretRef: - name: {{ .Values.bootAdminContainer.extraEnvVarsSecret }} - {{- end }} - {{- if .Values.bootAdminContainer.command }} - command: {{- include "cas-server.tplvalues.render" (dict "value" .Values.bootAdminContainer.command "context" $) | nindent 12 }} - {{- else }} - command: - - '/entrypoint.sh' - {{- end }} - {{- if .Values.bootAdminContainer.args }} - args: {{- include "cas-server.tplvalues.render" (dict "value" .Values.bootAdminContainer.args "context" $) | nindent 12 }} - {{- end }} - ports: - - name: https - containerPort: {{ .Values.bootadmin.listenPortHttps }} - protocol: TCP - - name: jvm-debug - containerPort: {{ .Values.bootadmin.listenPortJvmDebug }} - protocol: TCP - volumeMounts: - {{- range $.Values.bootAdminContainer.casConfigMounts }} - {{- $configMount := printf "%s-%s" "bootadmin-config" . | replace "." "-" | replace "_" "-" | lower }} - {{- $configMountPath := printf "%s/%s" "/etc/cas/config" . }} - - name: {{ $configMount | quote }} - mountPath: {{ $configMountPath }} - subPath: {{ . | quote }} - {{- end }} - - name: scripts - mountPath: /entrypoint.sh - subPath: bootadmin-entrypoint.sh - {{- if .Values.bootAdminContainer.serverKeystoreExistingSecret }} - - name: cas-server-keystore - mountPath: {{ .Values.bootAdminContainer.serverKeystoreMountPath }} - subPath: {{ .Values.bootAdminContainer.serverKeystoreSubPath }} - {{- end }} - {{- if .Values.bootAdminContainer.extraVolumeMounts }} - {{- include "cas-server.tplvalues.render" ( dict "value" .Values.bootAdminContainer.extraVolumeMounts "context" $ ) | nindent 12 }} - {{- end }} - startupProbe: - httpGet: - path: {{ .Values.bootAdminContainer.defaultStatusUrl }} - port: https - scheme: HTTPS - {{- if .Values.bootAdminContainer.defaultStatusHeaders }} - {{- include "cas-server.tplvalues.render" ( dict "value" .Values.bootAdminContainer.defaultStatusHeaders "context" $ ) | nindent 14 }} - {{- end }} - failureThreshold: {{ .Values.bootAdminContainer.startupFailureThreshold }} - periodSeconds: 20 - readinessProbe: - httpGet: - path: {{ .Values.bootAdminContainer.defaultStatusUrl }} - port: https - scheme: HTTPS - {{- if .Values.bootAdminContainer.defaultStatusHeaders }} - {{- include "cas-server.tplvalues.render" ( dict "value" .Values.bootAdminContainer.defaultStatusHeaders "context" $ ) | nindent 14 }} - {{- end }} - initialDelaySeconds: {{ .Values.bootAdminContainer.readinessInitialDelaySeconds }} - periodSeconds: 5 - failureThreshold: {{ .Values.bootAdminContainer.readinessFailureThreshold }} - livenessProbe: - httpGet: - path: {{ .Values.bootAdminContainer.defaultStatusUrl }} - port: https - scheme: HTTPS - {{- if .Values.bootAdminContainer.defaultStatusHeaders }} - {{- include "cas-server.tplvalues.render" ( dict "value" .Values.bootAdminContainer.defaultStatusHeaders "context" $ ) | nindent 14 }} - {{- end }} - initialDelaySeconds: {{ .Values.bootAdminContainer.livenessInitialDelaySeconds }} - periodSeconds: 15 - failureThreshold: {{ .Values.bootAdminContainer.livenessFailureThreshold }} - resources: - {{- toYaml .Values.bootadmin.resources | nindent 12 }} - {{- with .Values.bootadmin.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.bootadmin.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.bootadmin.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} -{{- end }} diff --git a/apereo-cas/helm/cas-server/templates/bootadmin/ingress.yaml b/apereo-cas/helm/cas-server/templates/bootadmin/ingress.yaml deleted file mode 100644 index f382ac4..0000000 --- a/apereo-cas/helm/cas-server/templates/bootadmin/ingress.yaml +++ /dev/null @@ -1,53 +0,0 @@ -{{- if and .Values.bootadmin.enabled .Values.bootadmin.ingress.enabled -}} -{{- $fullName := include "cas-server.bootadminname" . -}} -{{- $svcPort := .Values.bootadmin.service.port -}} -{{- $kubeVersion := .Capabilities.KubeVersion.Version -}} -{{- if semverCompare ">=1.19.0" $kubeVersion }} -apiVersion: networking.k8s.io/v1 -{{- else -}} -apiVersion: networking.k8s.io/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullName }} - labels: - {{- include "cas-server.labels" . | nindent 4 }} - {{- with .Values.bootadmin.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if .Values.bootadmin.ingress.tls }} - tls: - {{- range .Values.bootadmin.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} - rules: - {{- range .Values.bootadmin.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ . }} - {{- if semverCompare ">=1.18.0" $kubeVersion }} - pathType: Prefix - {{- end }} - {{- if semverCompare ">=1.19.0" $kubeVersion }} - backend: - service: - name: {{ $fullName }} - port: - number: {{ $svcPort }} - {{- else }} - backend: - serviceName: {{ $fullName }} - servicePort: {{ $svcPort }} - {{- end }} - {{- end }} - {{- end }} -{{- end }} diff --git a/apereo-cas/helm/cas-server/templates/bootadmin/service.yaml b/apereo-cas/helm/cas-server/templates/bootadmin/service.yaml deleted file mode 100644 index ab3f1a3..0000000 --- a/apereo-cas/helm/cas-server/templates/bootadmin/service.yaml +++ /dev/null @@ -1,17 +0,0 @@ -{{- if .Values.bootadmin.enabled -}} -apiVersion: v1 -kind: Service -metadata: - name: {{ include "cas-server.bootadminname" . }} - labels: - {{- include "cas-server.labels" . | nindent 4 }} -spec: - type: {{ .Values.bootadmin.service.type }} - ports: - - port: {{ .Values.bootadmin.service.port }} - targetPort: https - protocol: TCP - name: https - selector: - {{- include "cas-bootadmin.selectorLabels" . | nindent 4 }} -{{- end }} diff --git a/apereo-cas/helm/cas-server/templates/casconfig-configmap.yaml b/apereo-cas/helm/cas-server/templates/casconfig-configmap.yaml deleted file mode 100644 index 3f12168..0000000 --- a/apereo-cas/helm/cas-server/templates/casconfig-configmap.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "cas-server.fullname" . }}-casconfig - labels: {{- include "cas-server.labels" . | nindent 4 }} -data: - {{- include "cas-server.tplvalues.render" (dict "value" .Values.casServerContainer.casConfig "context" $) | nindent 2 }} diff --git a/apereo-cas/helm/cas-server/templates/ingress.yaml b/apereo-cas/helm/cas-server/templates/ingress.yaml deleted file mode 100644 index b17da9e..0000000 --- a/apereo-cas/helm/cas-server/templates/ingress.yaml +++ /dev/null @@ -1,53 +0,0 @@ -{{- if .Values.cas.ingress.enabled -}} -{{- $fullName := include "cas-server.fullname" . -}} -{{- $svcPort := .Values.cas.service.port -}} -{{- $kubeVersion := .Capabilities.KubeVersion.Version -}} -{{- if semverCompare ">=1.19.0" $kubeVersion }} -apiVersion: networking.k8s.io/v1 -{{- else -}} -apiVersion: networking.k8s.io/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullName }} - labels: - {{- include "cas-server.labels" . | nindent 4 }} - {{- with .Values.cas.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if .Values.cas.ingress.tls }} - tls: - {{- range .Values.cas.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} - rules: - {{- range .Values.cas.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ . }} - {{- if semverCompare ">=1.18.0" $kubeVersion }} - pathType: Prefix - {{- end }} - {{- if semverCompare ">=1.19.0" $kubeVersion }} - backend: - service: - name: {{ $fullName }} - port: - number: {{ $svcPort }} - {{- else }} - backend: - serviceName: {{ $fullName }} - servicePort: {{ $svcPort }} - {{- end }} - {{- end }} - {{- end }} - {{- end }} diff --git a/apereo-cas/helm/cas-server/templates/mgmt/configmap.yaml b/apereo-cas/helm/cas-server/templates/mgmt/configmap.yaml deleted file mode 100644 index 8dafa98..0000000 --- a/apereo-cas/helm/cas-server/templates/mgmt/configmap.yaml +++ /dev/null @@ -1,9 +0,0 @@ -{{- if .Values.mgmt.enabled -}} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "cas-server.mgmtname" . }}-config - labels: {{- include "cas-server.labels" . | nindent 4 }} -data: - {{- include "cas-server.tplvalues.render" (dict "value" .Values.mgmtContainer.casConfig "context" $) | nindent 2 }} -{{- end }} diff --git a/apereo-cas/helm/cas-server/templates/mgmt/deployment.yaml b/apereo-cas/helm/cas-server/templates/mgmt/deployment.yaml deleted file mode 100644 index 0287e5f..0000000 --- a/apereo-cas/helm/cas-server/templates/mgmt/deployment.yaml +++ /dev/null @@ -1,188 +0,0 @@ -{{- if .Values.mgmt.enabled -}} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "cas-server.mgmtname" . }} - labels: {{- include "cas-server.labels" . | nindent 4 }} -spec: - replicas: {{ .Values.mgmt.replicaCount }} - selector: - matchLabels: - {{- include "cas-mgmt.selectorLabels" . | nindent 6 }} - template: - metadata: - annotations: - {{- with .Values.podAnnotations }} - {{- toYaml . | nindent 8 }} - {{- end }} - {{ if .Values.mgmtContainer.alwaysRoll }} - rollme: {{ randAlphaNum 5 | quote }} - {{- else }} - rollme: "rolldisabled" - {{- end }} - labels: - {{- include "cas-mgmt.selectorLabels" . | nindent 8 }} - {{- include "cas-mgmt.labels" . | nindent 8 }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "cas-server.serviceAccountName" . }} - {{- if .Values.podSecurityContext.enabled }} - securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }} - {{- end }} - volumes: - {{- range $.Values.mgmtContainer.casConfigMounts }} - {{- $configMount := printf "%s-%s" "mgmt-config" . | replace "." "-" | replace "_" "-" | lower }} - - name: {{ $configMount | quote }} - configMap: - name: {{ include "cas-server.mgmtname" $ }}-config - defaultMode: 0444 - {{- end }} - - name: scripts - configMap: - name: {{ include "cas-server.fullname" . }}-scripts - defaultMode: 0555 - {{- if .Values.mgmtContainer.serverKeystoreExistingSecret }} - - name: cas-server-keystore - secret: - secretName: {{ .Values.mgmtContainer.serverKeystoreExistingSecret }} - defaultMode: 0444 - items: - - key: {{ .Values.mgmtContainer.serverKeystoreSubPath }} - path: {{ .Values.mgmtContainer.serverKeystoreSubPath }} - {{- end }} - {{- if .Values.mgmtContainer.extraVolumes }} - {{- include "cas-server.tplvalues.render" ( dict "value" .Values.mgmtContainer.extraVolumes "context" $ ) | nindent 10 }} - {{- end }} - containers: - - name: cas-mgmt - {{- if .Values.containerSecurityContext.enabled }} - securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} - {{- end }} - image: {{ include "cas-server.mgmtImageName" . }} - imagePullPolicy: {{ .Values.mgmtimage.pullPolicy }} - env: - {{- if .Values.mgmtContainer.warPath }} - - name: CAS_MGMT_WAR - value: {{ .Values.mgmtContainer.warPath | quote }} - {{- end }} - {{- if .Values.mgmtContainer.profiles }} - - name: CAS_SPRING_PROFILES - value: {{ .Values.mgmtContainer.profiles | quote }} - {{- end }} - {{- if .Values.mgmtContainer.jvm.maxHeapOpt }} - - name: MAX_HEAP_OPT - value: {{ .Values.mgmtContainer.jvm.maxHeapOpt | quote }} - {{- end }} - {{- if .Values.mgmtContainer.jvm.minHeapOpt }} - - name: MIN_HEAP_OPT - value: {{ .Values.mgmtContainer.jvm.minHeapOpt | quote }} - {{- end }} - {{- if .Values.mgmtContainer.jvm.extraOpts }} - - name: JVM_EXTRA_OPTS - value: {{ .Values.mgmtContainer.jvm.extraOpts | quote }} - {{- end }} - - name: JAVA_ENABLE_DEBUG - value: {{ .Values.mgmtContainer.jvm.debugEnabled | quote }} - - name: JAVA_DEBUG_SUSPEND - value: {{ .Values.mgmtContainer.jvm.debugSuspend | quote }} - - name: 'POD_IP' - valueFrom: - fieldRef: - fieldPath: status.podIP - {{- if .Values.mgmtContainer.extraEnvVars }} - {{- include "cas-server.tplvalues.render" (dict "value" .Values.mgmtContainer.extraEnvVars "context" $) | nindent 12 }} - {{- end }} - envFrom: - {{- if .Values.mgmtContainer.extraEnvVarsConfigMap }} - - configMapRef: - name: {{ .Values.mgmtContainer.extraEnvVarsConfigMap }} - {{- end }} - {{- if .Values.mgmtContainer.extraEnvVarsSecret }} - - secretRef: - name: {{ .Values.mgmtContainer.extraEnvVarsSecret }} - {{- end }} - {{- if .Values.mgmtContainer.command }} - command: {{- include "cas-server.tplvalues.render" (dict "value" .Values.mgmtContainer.command "context" $) | nindent 12 }} - {{- else }} - command: - - '/entrypoint.sh' - {{- end }} - {{- if .Values.mgmtContainer.args }} - args: {{- include "cas-server.tplvalues.render" (dict "value" .Values.mgmtContainer.args "context" $) | nindent 12 }} - {{- end }} - ports: - - name: https - containerPort: {{ .Values.mgmt.listenPortHttps }} - protocol: TCP - - name: jvm-debug - containerPort: {{ .Values.mgmt.listenPortJvmDebug }} - protocol: TCP - volumeMounts: - {{- range $.Values.mgmtContainer.casConfigMounts }} - {{- $configMount := printf "%s-%s" "mgmt-config" . | replace "." "-" | replace "_" "-" | lower }} - {{- $configMountPath := printf "%s/%s" "/etc/cas/config" . }} - - name: {{ $configMount | quote }} - mountPath: {{ $configMountPath }} - subPath: {{ . | quote }} - {{- end }} - - name: scripts - mountPath: /entrypoint.sh - subPath: mgmt-entrypoint.sh - {{- if .Values.mgmtContainer.serverKeystoreExistingSecret }} - - name: cas-server-keystore - mountPath: {{ .Values.mgmtContainer.serverKeystoreMountPath }} - subPath: {{ .Values.mgmtContainer.serverKeystoreSubPath }} - {{- end }} - {{- if .Values.mgmtContainer.extraVolumeMounts }} - {{- include "cas-server.tplvalues.render" ( dict "value" .Values.mgmtContainer.extraVolumeMounts "context" $ ) | nindent 12 }} - {{- end }} - startupProbe: - httpGet: - path: {{ .Values.mgmtContainer.defaultStatusUrl }} - port: https - scheme: HTTPS - {{- if .Values.mgmtContainer.defaultStatusHeaders }} - {{- include "cas-server.tplvalues.render" ( dict "value" .Values.mgmtContainer.defaultStatusHeaders "context" $ ) | nindent 14 }} - {{- end }} - failureThreshold: {{ .Values.mgmtContainer.startupFailureThreshold }} - periodSeconds: 20 - readinessProbe: - httpGet: - path: {{ .Values.mgmtContainer.defaultStatusUrl }} - port: https - scheme: HTTPS - {{- if .Values.mgmtContainer.defaultStatusHeaders }} - {{- include "cas-server.tplvalues.render" ( dict "value" .Values.mgmtContainer.defaultStatusHeaders "context" $ ) | nindent 14 }} - {{- end }} - initialDelaySeconds: {{ .Values.mgmtContainer.readinessInitialDelaySeconds }} - periodSeconds: 5 - failureThreshold: {{ .Values.mgmtContainer.readinessFailureThreshold }} - livenessProbe: - httpGet: - path: {{ .Values.mgmtContainer.defaultStatusUrl }} - port: https - scheme: HTTPS - {{- if .Values.mgmtContainer.defaultStatusHeaders }} - {{- include "cas-server.tplvalues.render" ( dict "value" .Values.mgmtContainer.defaultStatusHeaders "context" $ ) | nindent 14 }} - {{- end }} - initialDelaySeconds: {{ .Values.mgmtContainer.livenessInitialDelaySeconds }} - periodSeconds: 15 - failureThreshold: {{ .Values.mgmtContainer.livenessFailureThreshold }} - resources: - {{- toYaml .Values.mgmt.resources | nindent 12 }} - {{- with .Values.mgmt.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.mgmt.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.mgmt.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} -{{- end }} diff --git a/apereo-cas/helm/cas-server/templates/mgmt/ingress.yaml b/apereo-cas/helm/cas-server/templates/mgmt/ingress.yaml deleted file mode 100644 index 5bb272e..0000000 --- a/apereo-cas/helm/cas-server/templates/mgmt/ingress.yaml +++ /dev/null @@ -1,53 +0,0 @@ -{{- if and .Values.mgmt.enabled .Values.mgmt.ingress.enabled -}} -{{- $fullName := include "cas-server.mgmtname" . -}} -{{- $svcPort := .Values.mgmt.service.port -}} -{{- $kubeVersion := .Capabilities.KubeVersion.Version -}} -{{- if semverCompare ">=1.19.0" $kubeVersion }} -apiVersion: networking.k8s.io/v1 -{{- else -}} -apiVersion: networking.k8s.io/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullName }} - labels: - {{- include "cas-server.labels" . | nindent 4 }} - {{- with .Values.mgmt.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if .Values.mgmt.ingress.tls }} - tls: - {{- range .Values.mgmt.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} - rules: - {{- range .Values.mgmt.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ . }} - {{- if semverCompare ">=1.18.0" $kubeVersion }} - pathType: Prefix - {{- end }} - {{- if semverCompare ">=1.19.0" $kubeVersion }} - backend: - service: - name: {{ $fullName }} - port: - number: {{ $svcPort }} - {{- else }} - backend: - serviceName: {{ $fullName }} - servicePort: {{ $svcPort }} - {{- end }} - {{- end }} - {{- end }} -{{- end }} diff --git a/apereo-cas/helm/cas-server/templates/mgmt/service.yaml b/apereo-cas/helm/cas-server/templates/mgmt/service.yaml deleted file mode 100644 index 0b1b942..0000000 --- a/apereo-cas/helm/cas-server/templates/mgmt/service.yaml +++ /dev/null @@ -1,17 +0,0 @@ -{{- if .Values.mgmt.enabled -}} -apiVersion: v1 -kind: Service -metadata: - name: {{ include "cas-server.mgmtname" . }} - labels: - {{- include "cas-server.labels" . | nindent 4 }} -spec: - type: {{ .Values.mgmt.service.type }} - ports: - - port: {{ .Values.mgmt.service.port }} - targetPort: https - protocol: TCP - name: https - selector: - {{- include "cas-mgmt.selectorLabels" . | nindent 4 }} -{{- end }} diff --git a/apereo-cas/helm/cas-server/templates/role.yaml b/apereo-cas/helm/cas-server/templates/role.yaml deleted file mode 100644 index 3522656..0000000 --- a/apereo-cas/helm/cas-server/templates/role.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.rbac.create -}} -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ include "cas-server.fullname" . }} - labels: - {{- include "cas-server.labels" . | nindent 4 }} -rules: -- apiGroups: ["", "extensions", "apps"] - resources: ["configmaps", "pods", "services", "endpoints", "secrets"] - verbs: ["get", "list", "watch"] -{{- end -}} diff --git a/apereo-cas/helm/cas-server/templates/rolebinding.yaml b/apereo-cas/helm/cas-server/templates/rolebinding.yaml deleted file mode 100644 index efbd546..0000000 --- a/apereo-cas/helm/cas-server/templates/rolebinding.yaml +++ /dev/null @@ -1,16 +0,0 @@ -{{- if .Values.rbac.create -}} -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ include "cas-server.fullname" . }} - labels: - {{- include "cas-server.labels" . | nindent 4 }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ include "cas-server.fullname" . }} -subjects: -- kind: ServiceAccount - name: {{ template "cas-server.serviceAccountName" . }} - namespace: {{ .Release.Namespace }} -{{ end }} diff --git a/apereo-cas/helm/cas-server/templates/script-configmap.yaml b/apereo-cas/helm/cas-server/templates/script-configmap.yaml deleted file mode 100644 index dc1cd98..0000000 --- a/apereo-cas/helm/cas-server/templates/script-configmap.yaml +++ /dev/null @@ -1,54 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "cas-server.fullname" . }}-scripts - labels: {{- include "cas-server.labels" . | nindent 4 }} -data: - entrypoint.sh: |- - #!/bin/sh - echo Working Directory: $(pwd) - # Set debug options if required - JAVA_DEBUG_ARGS= - if [ "${JAVA_ENABLE_DEBUG}" == "true" ]; then - JAVA_DEBUG_ARGS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=${JAVA_DEBUG_SUSPEND:-n},address=${JAVA_DEBUG_PORT:-5005}" - echo "Run the following to forward local port to pod:" - echo "kubectl port-forward $HOSTNAME ${JAVA_DEBUG_PORT:-5005}:${JAVA_DEBUG_PORT:-5005}" - fi - PROFILE_OPT= - if [ ! -z $CAS_SPRING_PROFILES ]; then - PROFILE_OPT="--spring.profiles.active=$CAS_SPRING_PROFILES" - fi - echo java -server -noverify $JAVA_DEBUG_ARGS $MAX_HEAP_OPT $NEW_HEAP_OPT $JVM_EXTRA_OPTS -jar $CAS_WAR $PROFILE_OPT $@ - exec java -server -noverify $JAVA_DEBUG_ARGS $MAX_HEAP_OPT $NEW_HEAP_OPT $JVM_EXTRA_OPTS -jar $CAS_WAR $PROFILE_OPT $@ - bootadmin-entrypoint.sh: |- - #!/bin/sh - echo Working Directory: $(pwd) - # Set debug options if required - JAVA_DEBUG_ARGS= - if [ "${JAVA_ENABLE_DEBUG}" == "true" ]; then - echo "Run the following to forward local port to pod:" - echo "kubectl port-forward $HOSTNAME ${JAVA_DEBUG_PORT:-5005}:${JAVA_DEBUG_PORT:-5005}" - JAVA_DEBUG_ARGS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=${JAVA_DEBUG_SUSPEND:-n},address=${JAVA_DEBUG_PORT:-5005}" - fi - PROFILE_OPT= - if [ ! -z $CAS_SPRING_PROFILES ]; then - PROFILE_OPT="--spring.profiles.active=$CAS_SPRING_PROFILES" - fi - echo java -server -noverify $JAVA_DEBUG_ARGS $MAX_HEAP_OPT $NEW_HEAP_OPT $JVM_EXTRA_OPTS -jar $CAS_BOOTADMIN_WAR $PROFILE_OPT $@ - exec java -server -noverify $JAVA_DEBUG_ARGS $MAX_HEAP_OPT $NEW_HEAP_OPT $JVM_EXTRA_OPTS -jar $CAS_BOOTADMIN_WAR $PROFILE_OPT $@ - mgmt-entrypoint.sh: |- - #!/bin/sh - echo Working Directory: $(pwd) - # Set debug options if required - JAVA_DEBUG_ARGS= - if [ "${JAVA_ENABLE_DEBUG}" == "true" ]; then - echo "Run the following to forward local port to pod:" - echo "kubectl port-forward $HOSTNAME ${JAVA_DEBUG_PORT:-5005}:${JAVA_DEBUG_PORT:-5005}" - JAVA_DEBUG_ARGS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=${JAVA_DEBUG_SUSPEND:-n},address=${JAVA_DEBUG_PORT:-5005}" - fi - PROFILE_OPT= - if [ ! -z $CAS_SPRING_PROFILES ]; then - PROFILE_OPT="--spring.profiles.active=$CAS_SPRING_PROFILES" - fi - echo java -server -noverify $JAVA_DEBUG_ARGS $MAX_HEAP_OPT $NEW_HEAP_OPT $JVM_EXTRA_OPTS -jar $CAS_MGMT_WAR $PROFILE_OPT $@ - exec java -server -noverify $JAVA_DEBUG_ARGS $MAX_HEAP_OPT $NEW_HEAP_OPT $JVM_EXTRA_OPTS -jar $CAS_MGMT_WAR $PROFILE_OPT $@ diff --git a/apereo-cas/helm/cas-server/templates/service.yaml b/apereo-cas/helm/cas-server/templates/service.yaml deleted file mode 100644 index 92a069a..0000000 --- a/apereo-cas/helm/cas-server/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ include "cas-server.fullname" . }} - labels: - {{- include "cas-server.labels" . | nindent 4 }} -spec: - type: {{ .Values.cas.service.type }} - ports: - - port: {{ .Values.cas.service.port }} - targetPort: https - protocol: TCP - name: https - selector: - {{- include "cas-server.selectorLabels" . | nindent 4 }} diff --git a/apereo-cas/helm/cas-server/templates/serviceaccount.yaml b/apereo-cas/helm/cas-server/templates/serviceaccount.yaml deleted file mode 100644 index 3fef810..0000000 --- a/apereo-cas/helm/cas-server/templates/serviceaccount.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.serviceAccount.create -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "cas-server.serviceAccountName" . }} - labels: - {{- include "cas-server.labels" . | nindent 4 }} - {{- with .Values.serviceAccount.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -{{- end }} diff --git a/apereo-cas/helm/cas-server/templates/statefulset.yaml b/apereo-cas/helm/cas-server/templates/statefulset.yaml deleted file mode 100644 index 595449f..0000000 --- a/apereo-cas/helm/cas-server/templates/statefulset.yaml +++ /dev/null @@ -1,263 +0,0 @@ -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ include "cas-server.fullname" . }} - labels: {{- include "cas-server.labels" . | nindent 4 }} -spec: - replicas: {{ .Values.replicaCount }} - updateStrategy: - type: {{ .Values.updateStrategy | quote}} - serviceName: {{ include "cas-server.fullname" . }} - podManagementPolicy: {{ .Values.podManagementPolicy | quote}} - selector: - matchLabels: - {{- include "cas-server.selectorLabels" . | nindent 6 }} - template: - metadata: - annotations: - {{- with .Values.podAnnotations }} - {{- toYaml . | nindent 8 }} - {{- end }} - {{ if .Values.casServerContainer.alwaysRoll }} - rollme: {{ randAlphaNum 5 | quote }} - {{- else }} - rollme: "rolldisabled" - {{- end }} - labels: - {{- include "cas-server.selectorLabels" . | nindent 8 }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "cas-server.serviceAccountName" . }} - {{- if .Values.podSecurityContext.enabled }} - securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }} - {{- end }} - volumes: - {{- range $.Values.casServerContainer.casConfigMounts }} - {{- $configMount := printf "%s-%s" "cas-config" . | replace "." "-" | replace "_" "-" | lower }} - - name: {{ $configMount | quote }} - configMap: - name: {{ include "cas-server.fullname" $ }}-casconfig - defaultMode: 0644 - {{- end }} - - name: scripts - configMap: - name: {{ include "cas-server.fullname" . }}-scripts - defaultMode: 0755 - - name: logdir - {{- include "cas-server.logdir" . | nindent 10 }} - {{- if .Values.casServerContainer.serverKeystoreExistingSecret }} - - name: cas-server-keystore - secret: - secretName: {{ .Values.casServerContainer.serverKeystoreExistingSecret }} - defaultMode: 0444 - items: - - key: {{ .Values.casServerContainer.serverKeystoreSubPath }} - path: {{ .Values.casServerContainer.serverKeystoreSubPath }} - {{- end }} - {{- if .Values.casServerContainer.extraVolumes }} - {{- include "cas-server.tplvalues.render" ( dict "value" .Values.casServerContainer.extraVolumes "context" $ ) | nindent 8 }} - {{- end }} - {{- if or .Values.casServerContainer.initContainers (and .Values.podSecurityContext.enabled .Values.volumePermissions.enabled .Values.persistence.enabled) }} - initContainers: - {{- if and .Values.podSecurityContext.enabled .Values.volumePermissions.enabled .Values.persistence.enabled }} - - name: volume-permissions - image: {{ include "cas-server.volumePermissions.image" . }} - imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} - command: - - /bin/sh - - -cx - - | - {{- if .Values.persistence.enabled }} - {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} - chown `id -u`:`id -G | cut -d " " -f2` {{ .Values.persistence.mountPath }} - {{- else }} - chown {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }} {{ .Values.persistence.mountPath }} - {{- end }} - mkdir -p {{ .Values.persistence.mountPath }}/data - chmod 700 {{ .Values.persistence.mountPath }}/data - find {{ .Values.persistence.mountPath }} -mindepth 1 -maxdepth 1 -not -name ".snapshot" -not -name "lost+found" | \ - {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} - xargs chown -R `id -u`:`id -G | cut -d " " -f2` - {{- else }} - xargs chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }} - {{- end }} - {{- end }} - {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} - securityContext: {{- omit .Values.volumePermissions.securityContext "runAsUser" | toYaml | nindent 12 }} - {{- else }} - securityContext: {{- .Values.volumePermissions.securityContext | toYaml | nindent 12 }} - {{- end }} - {{- if .Values.volumePermissions.resources }} - resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }} - {{- end }} - volumeMounts: - - name: data - mountPath: {{ .Values.persistence.mountPath }} - {{- end }} - {{- if .Values.casServerContainer.initContainers }} - {{- include "cas-server.tplvalues.render" (dict "value" .Values.casServerContainer.initContainers "context" $) | nindent 8 }} - {{- end }} - {{- end }} - containers: - - name: {{ .Chart.Name }} - {{- if .Values.containerSecurityContext.enabled }} - securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} - {{- end }} - image: {{ include "cas-server.imageName" . }} - imagePullPolicy: {{ .Values.image.pullPolicy }} - env: - {{- if .Values.casServerContainer.warPath }} - - name: CAS_WAR - value: {{ .Values.casServerContainer.warPath | quote }} - {{- end }} - {{- if .Values.casServerContainer.profiles }} - - name: CAS_SPRING_PROFILES - value: {{ .Values.casServerContainer.profiles | quote }} - {{- end }} - {{- if .Values.casServerContainer.jvm.maxHeapOpt }} - - name: MAX_HEAP_OPT - value: {{ .Values.casServerContainer.jvm.maxHeapOpt | quote }} - {{- end }} - {{- if .Values.casServerContainer.jvm.minHeapOpt }} - - name: MIN_HEAP_OPT - value: {{ .Values.casServerContainer.jvm.minHeapOpt | quote }} - {{- end }} - {{- if .Values.casServerContainer.jvm.extraOpts }} - - name: JVM_EXTRA_OPTS - value: {{ .Values.casServerContainer.jvm.extraOpts | quote }} - {{- end }} - - name: JAVA_ENABLE_DEBUG - value: {{ .Values.casServerContainer.jvm.debugEnabled | quote }} - - name: JAVA_DEBUG_SUSPEND - value: {{ .Values.casServerContainer.jvm.debugSuspend | quote }} - - name: 'KUBERNETES_NAMESPACE' # used by org.apache.catalina.tribes.membership.cloud.CloudMembershipProvider - value: {{ .Release.Namespace }} - - name: 'POD_IP' - valueFrom: - fieldRef: - fieldPath: status.podIP - {{- if .Values.casServerContainer.extraEnvVars }} - {{- include "cas-server.tplvalues.render" (dict "value" .Values.casServerContainer.extraEnvVars "context" $) | nindent 12 }} - {{- end }} - envFrom: - {{- if .Values.casServerContainer.extraEnvVarsConfigMap }} - - configMapRef: - name: {{ .Values.casServerContainer.extraEnvVarsConfigMap }} - {{- end }} - {{- if .Values.casServerContainer.extraEnvVarsSecret }} - - secretRef: - name: {{ .Values.casServerContainer.extraEnvVarsSecret }} - {{- end }} - {{- if .Values.casServerContainer.command }} - command: {{- include "cas-server.tplvalues.render" (dict "value" .Values.casServerContainer.command "context" $) | nindent 12 }} - {{- else }} - command: - - '/entrypoint.sh' - {{- end }} - {{- if .Values.casServerContainer.args }} - args: {{- include "cas-server.tplvalues.render" (dict "value" .Values.casServerContainer.args "context" $) | nindent 12 }} - {{- end }} - ports: - - name: https - containerPort: {{ .Values.cas.listenPortHttps }} - protocol: TCP - - name: jvm-debug - containerPort: {{ .Values.cas.listenPortJvmDebug }} - protocol: TCP - volumeMounts: - {{- if .Values.persistence.enabled }} - - name: data - mountPath: {{ .Values.persistence.mountPath }} - {{- end }} - {{- range $.Values.casServerContainer.casConfigMounts }} - {{- $configMount := printf "%s-%s" "cas-config" . | replace "." "-" | replace "_" "-" | lower }} - {{- $configMountPath := printf "%s/%s" "/etc/cas/config" . }} - - name: {{ $configMount | quote }} - mountPath: {{ $configMountPath }} - subPath: {{ . | quote }} - {{- end }} - - name: scripts - mountPath: /entrypoint.sh - subPath: entrypoint.sh - - name: logdir - mountPath: {{ .Values.logdir.mountPath }} - {{- if .Values.casServerContainer.serverKeystoreExistingSecret }} - - name: cas-server-keystore - mountPath: {{ .Values.casServerContainer.serverKeystoreMountPath }} - subPath: {{ .Values.casServerContainer.serverKeystoreSubPath }} - {{- end }} - {{- if .Values.casServerContainer.extraVolumeMounts }} - {{- include "cas-server.tplvalues.render" ( dict "value" .Values.casServerContainer.extraVolumeMounts "context" $ ) | nindent 12 }} - {{- end }} - startupProbe: - httpGet: - path: {{ .Values.casServerContainer.defaultStatusUrl }} - port: https - scheme: HTTPS - {{- if .Values.casServerContainer.defaultStatusHeaders }} - {{- include "cas-server.tplvalues.render" ( dict "value" .Values.casServerContainer.defaultStatusHeaders "context" $ ) | nindent 14 }} - {{- end }} - failureThreshold: {{ .Values.casServerContainer.startupFailureThreshold }} - periodSeconds: 20 - readinessProbe: - httpGet: - path: {{ .Values.casServerContainer.defaultStatusUrl }} - port: https - scheme: HTTPS - {{- if .Values.casServerContainer.defaultStatusHeaders }} - {{- include "cas-server.tplvalues.render" ( dict "value" .Values.casServerContainer.defaultStatusHeaders "context" $ ) | nindent 14 }} - {{- end }} - initialDelaySeconds: {{ .Values.casServerContainer.readinessInitialDelaySeconds }} - periodSeconds: 5 - failureThreshold: {{ .Values.casServerContainer.readinessFailureThreshold }} - livenessProbe: - httpGet: - path: {{ .Values.casServerContainer.defaultStatusUrl }} - port: https - scheme: HTTPS - {{- if .Values.casServerContainer.defaultStatusHeaders }} - {{- include "cas-server.tplvalues.render" ( dict "value" .Values.casServerContainer.defaultStatusHeaders "context" $ ) | nindent 14 }} - {{- end }} - initialDelaySeconds: {{ .Values.casServerContainer.livenessInitialDelaySeconds }} - periodSeconds: 15 - failureThreshold: {{ .Values.casServerContainer.livenessFailureThreshold }} - resources: - {{- toYaml .Values.resources | nindent 12 }} - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} -{{- if .Values.persistence.enabled }} - volumeClaimTemplates: - - metadata: - name: data - {{- with .Values.persistence.annotations }} - annotations: - {{- range $key, $value := . }} - {{ $key }}: {{ $value }} - {{- end }} - {{- end }} - spec: - accessModes: - {{- range .Values.persistence.accessModes }} - - {{ . | quote }} - {{- end }} - resources: - requests: - storage: {{ .Values.persistence.size | quote }} - storageClassName: {{ .Values.persistence.storageClassName | quote }} - {{- if .Values.persistence.selector }} - selector: {{- include "cas-server.tplvalues.render" (dict "value" .Values.persistence.selector "context" $) | nindent 10 }} - {{- end -}} -{{- end }} \ No newline at end of file diff --git a/apereo-cas/helm/cas-server/templates/tests/test-bootadmin.yaml b/apereo-cas/helm/cas-server/templates/tests/test-bootadmin.yaml deleted file mode 100644 index 30271e6..0000000 --- a/apereo-cas/helm/cas-server/templates/tests/test-bootadmin.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{ include "cas-server.bootadminname" . }}-test" - labels: - {{- include "cas-server.labels" . | nindent 4 }} - annotations: - "helm.sh/hook": test-success -spec: - containers: - - name: wget - image: alpine - command: ['wget'] - args: [ '--no-check-certificate', 'https://{{ include "cas-server.bootadminname" . }}:{{ .Values.bootadmin.service.port }}{{ .Values.bootAdminContainer.defaultStatusUrl }}' ] - restartPolicy: Never - diff --git a/apereo-cas/helm/cas-server/templates/tests/test-cas-server.yaml b/apereo-cas/helm/cas-server/templates/tests/test-cas-server.yaml deleted file mode 100644 index 2ca0f14..0000000 --- a/apereo-cas/helm/cas-server/templates/tests/test-cas-server.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{ include "cas-server.fullname" . }}-test" - labels: - {{- include "cas-server.labels" . | nindent 4 }} - annotations: - "helm.sh/hook": test-success -spec: - containers: - - name: wget - image: alpine - command: ['wget'] - args: [ '--no-check-certificate', 'https://{{ include "cas-server.fullname" . }}:{{ .Values.cas.service.port }}{{ .Values.casServerContainer.defaultStatusUrl }}' ] - restartPolicy: Never - diff --git a/apereo-cas/helm/cas-server/values.yaml b/apereo-cas/helm/cas-server/values.yaml deleted file mode 100644 index f0c34a4..0000000 --- a/apereo-cas/helm/cas-server/values.yaml +++ /dev/null @@ -1,782 +0,0 @@ -# Default values for cas-server. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -casServerName: cas.example.org -casMgmtServerName: casmgmt.example.org - -replicaCount: 1 - -image: - registry: "" - repository: "apereo/cas" - pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. - tag: "latest" - -bootadminimage: - registry: "" - repository: "apereo/cas-bootadmin-server" - pullPolicy: IfNotPresent - tag: "latest" - -mgmtimage: - registry: "" - repository: "apereo/cas-management" - pullPolicy: IfNotPresent - tag: "latest" - -imagePullSecrets: [] -nameOverride: "" -fullnameOverride: "" - -# There are two valid stateful set update strategies, RollingUpdate and the (legacy) OnDelete -updateStrategy: RollingUpdate - -# 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. -# When scaling down, the pods are removed in the opposite order. -# Parallel: Creates pods in parallel to match the desired scale without waiting, and on scale down will delete all pods at once. -podManagementPolicy: OrderedReady - -# Map folder for logs directory from host or pvc, or leave both blank to use emptyDir volume -# In docker for windows hostPath could be '/host_mnt/c/opt/cas/logs' -# Windows: Give full access local Users group to the to ~/.docker folder if getting permission denied) -logdir: -# hostPath: '/host_mnt/c/opt/cas/logs' - hostPath: '' - claimName: '' - mountPath: '/var/log' - -# CAS Server container properties -casServerContainer: - ## Roll on upgrade changes deployment when helm upgrade runs, forcing pod to restart - alwaysRoll: false - ## JVM Settings - ## JVM settings only used if command not set, use args to set app arguments - jvm: - ## Extra JVM options - ## - extraOpts: '-Djavax.net.ssl.trustStore=/etc/cas/truststore -Djavax.net.ssl.trustStoreType=PKCS12 -Djavax.net.ssl.trustStorePassword=changeit' - - ## Memory settings: If these aren't defined, java will calc values automatically, but requires setting limits on pod - ## so it doesn't base heap size on host memory - maxHeapOpt: '-Xmx2G' - newHeapOpt: '-Xms600M' - debugEnabled: true - debugSuspend: "n" # could be n or y, must quote or yaml changes to boolean - warPath: 'cas.war' - ## Override cmd - ## - command: - ## Override args - ## - args: - ## extraVolumes and extraVolumeMounts allows you to mount other volumes - ## Examples: - ## extraVolumeMounts: - ## - name: extras - ## mountPath: /usr/share/extras - ## readOnly: true - ## extraVolumes: - ## - name: extras - ## emptyDir: {} - ## - profiles: 'standalone' - - extraVolumeMounts: - - name: truststore - mountPath: /etc/cas/truststore - subPath: truststore - - extraVolumes: - - name: truststore - configMap: - name: cas-truststore - defaultMode: 0444 - - ## Url to use for readiness, startupprobe, and liveliness check, change to health actuator if the module is available - ## Naming it "default" in case in future template supports individual urls for the different checks, with this as default if they aren't specified - defaultStatusUrl: '/cas/actuator/health' - - # number of startup probe failures before it will be killed, set high if trying to debug startup issues - # liveness and readiness failure threshold might be 1 but startup failure threshold accounts for - # failures while server is starting up - startupFailureThreshold: 30 - livenessFailureThreshold: 1 - readinessFailureThreshold: 1 - readinessInitialDelaySeconds: 45 - livenessInitialDelaySeconds: 120 - - ## Extra init containers to add to the statefulset - ## - initContainers: [] - - ## An array to add extra env vars - ## For example: - ## extraEnvVars: - ## - name: MY_ENV_VAR - ## value: env_var_value - ## - extraEnvVars: [] - - ## Name of a ConfigMap containing extra env vars - ## - extraEnvVarsConfigMap: '' - - # name of secret containing server keystore - serverKeystoreExistingSecret: cas-server-keystore - # folder that should container the keystore - serverKeystoreMountPath: '/etc/cas/thekeystore' - # name of keystore file in container and in secret - serverKeystoreSubPath: 'thekeystore' - - ## Name of a Secret containing extra env vars - ## - extraEnvVarsSecret: '' - ## Choose which config files from casConfig to mount - casConfigMounts: - - 'cas.properties' - - 'cas.yaml' - ## Create various config files from casConfig that may or may not be mounted - casConfig: - # issue with line breaks? means can't use {{}} variables after first line - # workaround is to use {{}} variables in yaml version of properties file - cas.properties: |- - cas.server.name=https://{{ .Values.casServerName }} - context.path=/cas - cas.server.prefix=${cas.server.name}${context.path} - - cas.http-client.truststore.psw=changeit - cas.http-client.truststore.file=/etc/cas/truststore - - # put web access logs in same directory as cas logs - cas.server.tomcat.ext-access-log.directory=/var/log - cas.server.tomcat.ext-access-log.enabled=true - - # uncomment the folowing to not allow login of built-in users - # cas.authn.accept.users= - - # since we are behind ingress controller, need to use x-forwarded-for to get client ip - # if nginx ingress controller is behind another proxy, it needs to be configured globally with the following settings in the ingress controller configmap - # use-forwarded-headers: "true" # very important for CAS or any app that compares IP being used against IP that initiated sessions (session fixation) - # 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 - cas.audit.engine.alternate-client-addr-header-name=X-Forwarded-For - server.tomcat.remoteip.remote-ip-header=X-FORWARDED-FOR - - server.ssl.key-store=file:/etc/cas/thekeystore - server.ssl.key-store-type=PKCS12 - server.ssl.key-store-password=changeit - server.ssl.trust-store=file:/etc/cas/truststore - server.ssl.trust-store-type=PKCS12 - server.ssl.trust-store-password=changeit - - # expose endpoints via http - management.endpoints.web.exposure.include=health,info,prometheus,metrics,env,loggers,statistics,status,loggingConfig,events,configurationMetadata,caches - management.endpoints.web.base-path=/actuator - management.endpoints.web.cors.allowed-origins=https://${cas-host} - management.endpoints.web.cors.allowed-methods=GET,POST - - # enable endpoints - management.endpoint.metrics.enabled=true - management.endpoint.health.enabled=true - management.endpoint.info.enabled=true - management.endpoint.env.enabled=true - management.endpoint.loggers.enabled=true - management.endpoint.status.enabled=true - management.endpoint.statistics.enabled=true - management.endpoint.prometheus.enabled=true - management.endpoint.events.enabled=true - management.endpoint.loggingConfig.enabled=true - management.endpoint.configurationMetadata.enabled=true - # configure health endpoint - management.health.defaults.enabled=false - management.health.ping.enabled=true - management.health.caches.enabled=true - - # secure endpoints to localhost - - cas.monitor.endpoints.endpoint.defaults.access[0]=AUTHENTICATED - cas.monitor.endpoints.endpoint.health.access[0]=IP_ADDRESS - cas.monitor.endpoints.endpoint.health.requiredIpAddresses[0]=127.0.0.1 - cas.monitor.endpoints.endpoint.health.requiredIpAddresses[1]=0:0:0:0:0:0:0:1 - cas.monitor.endpoints.endpoint.health.requiredIpAddresses[2]=10\\..* - cas.monitor.endpoints.endpoint.health.requiredIpAddresses[3]=172\\.16\\..* - cas.monitor.endpoints.endpoint.health.requiredIpAddresses[4]=192\\.168\\..* - - spring.boot.admin.client.enabled=true - #eof - - cas.yaml: |- - --- - logging: - config: 'file:/etc/cas/config/log4j2.xml' - cas: - server: - tomcat: - clustering: - enabled: true - clustering-type: 'CLOUD' - cloud-membership-provider: 'kubernetes' - spring: - security: - user: - name: "{{ .Values.casAdminUser }}" - password: "{{ .Values.casAdminPassword }}" - boot: - admin: - client: - username: {{ .Values.bootAdminUser }} - password: {{ .Values.bootAdminPassword }} - url: https://{{ include "cas-server.bootadminname" . }}:8443 - instance: - metadata: - user: - name: "{{ .Values.casAdminUser }}" - password: "{{ .Values.casAdminPassword }}" - management-base-url: https://${HOSTNAME}.{{ include "cas-server.fullname" . }}.{{ .Release.Namespace }}.svc:8443${context.path} - #eof - - - -# CAS Boot-Admin Server container properties -bootAdminContainer: - ## Roll on upgrade changes deployment when helm upgrade runs, forcing pod to restart - alwaysRoll: false - ## JVM Settings - ## JVM settings only used if command not set, use args to set app arguments - jvm: - ## Extra JVM options - ## - extraOpts: '-Djavax.net.ssl.trustStore=/etc/cas/truststore -Djavax.net.ssl.trustStoreType=PKCS12 -Djavax.net.ssl.trustStorePassword=changeit' - ## Memory settings: If these aren't defined, java will calc values automatically, but requires setting limits on pod - ## so it doesn't base heap size on host memory - maxHeapOpt: -Xmx1G - newHeapOpt: -Xms250M - debugEnabled: true - debugSuspend: "n" # could be n or y, must quote or yaml changes to boolean - warPath: 'casbootadminserver.war' - ## Override cmd - ## - command: - ## Override args - ## - args: - - '--spring.config.additional-location=file:/etc/cas/config/' # directories should end in / and have protocol - - '--logging.config=file:/etc/cas/config/log4j2.xml' - - profiles: 'standalone' - - ## extraVolumes and extraVolumeMounts allows you to mount other volumes - ## Examples: - ## extraVolumeMounts: - ## - name: extras - ## mountPath: /usr/share/extras - ## readOnly: true - ## extraVolumes: - ## - name: extras - ## emptyDir: {} - ## - extraVolumeMounts: - - name: truststore - mountPath: /etc/cas/truststore - subPath: truststore - - extraVolumes: - - name: truststore - configMap: - name: cas-truststore - defaultMode: 0444 - - ## Url to use for readiness, startupprobe, and liveliness check, change to health actuator if the module is available - ## Naming it "default" in case in future template supports individual urls for the different checks, with this as default if they aren't specified - defaultStatusUrl: '/login' - - # number of startup probe failures before it will be killed, set high if trying to debug startup issues - # liveness and readiness failure threshold might be 1 but startup failure threshold accounts for - # failures while server is starting up - startupFailureThreshold: 30 - livenessFailureThreshold: 1 - readinessFailureThreshold: 1 - readinessInitialDelaySeconds: 45 - livenessInitialDelaySeconds: 120 - - ## An array to add extra env vars - ## For example: - ## extraEnvVars: - ## - name: MY_ENV_VAR - ## value: env_var_value - ## - extraEnvVars: [] - - ## Name of a ConfigMap containing extra env vars - ## - extraEnvVarsConfigMap: '' - - # name of secret containing bootadmin server keystore - serverKeystoreExistingSecret: cas-server-keystore - # folder that should container the keystore - serverKeystoreMountPath: '/etc/cas/thekeystore' - # name of keystore file in container and in secret - serverKeystoreSubPath: 'thekeystore' - - ## Name of a Secret containing extra env vars - ## - extraEnvVarsSecret: '' - ## Choose which config files from casConfig to mount - casConfigMounts: - - 'application.properties' - - 'application.yml' - - 'log4j2.xml' - ## Create various config files from casConfig that may or may not be mounted - casConfig: - application.properties: |- - server.ssl.enabled=true - server.ssl.key-store=file:/etc/cas/thekeystore - server.ssl.key-store-type=PKCS12 - server.ssl.key-store-password=changeit - server.ssl.trust-store=file:/etc/cas/truststore - server.ssl.trust-store-type=PKCS12 - server.ssl.trust-store-password=changeit - - application.yml: |- - --- - spring: - security: - user: - name: "{{ .Values.bootAdminUser }}" - password: "{{ .Values.bootAdminPassword }}" - - log4j2.xml: |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# CAS Management Server container properties -mgmtContainer: - ## Roll on upgrade changes deployment when helm upgrade runs, forcing pod to restart - alwaysRoll: false - ## JVM Settings - ## JVM settings only used if command not set, use args to set app arguments - jvm: - ## Extra JVM options - ## - extraOpts: '-Djavax.net.ssl.trustStore=/etc/cas/truststore -Djavax.net.ssl.trustStoreType=PKCS12 -Djavax.net.ssl.trustStorePassword=changeit' - ## Memory settings: If these aren't defined, java will calc values automatically, but requires setting limits on pod - ## so it doesn't base heap size on host memory - maxHeapOpt: -Xmx1G - newHeapOpt: -Xms250M - debugEnabled: true - debugSuspend: "n" # could be n or y, must quote or yaml changes to boolean - warPath: 'cas-management.war' - ## Override cmd - ## - command: - ## Override args - ## - args: - - '--spring.config.additional-location=file:/etc/cas/config/' # directories should end in / and have protocol - - '--logging.config=file:/etc/cas/config/log4j2.xml' - - profiles: 'standalone' - - ## extraVolumes and extraVolumeMounts allows you to mount other volumes - ## Examples: - ## extraVolumeMounts: - ## - name: extras - ## mountPath: /usr/share/extras - ## readOnly: true - ## extraVolumes: - ## - name: extras - ## emptyDir: {} - ## - extraVolumeMounts: - - name: truststore - mountPath: /etc/cas/truststore - subPath: truststore - - extraVolumes: - - name: truststore - configMap: - name: cas-truststore - defaultMode: 0444 - - ## Url to use for readiness, startupprobe, and liveliness check, change to health actuator if the module is available - ## Naming it "default" in case in future template supports individual urls for the different checks, with this as default if they aren't specified - defaultStatusUrl: '/cas-management/actuator/health' - - # number of startup probe failures before it will be killed, set high if trying to debug startup issues - # liveness and readiness failure threshold might be 1 but startup failure threshold accounts for - # failures while server is starting up - startupFailureThreshold: 30 - livenessFailureThreshold: 1 - readinessFailureThreshold: 1 - readinessInitialDelaySeconds: 45 - livenessInitialDelaySeconds: 120 - - ## An array to add extra env vars - ## For example: - ## extraEnvVars: - ## - name: MY_ENV_VAR - ## value: env_var_value - ## - extraEnvVars: [] - - ## Name of a ConfigMap containing extra env vars - ## - extraEnvVarsConfigMap: '' - - # name of secret containing mgmt server keystore - serverKeystoreExistingSecret: cas-server-keystore - # folder that should container the keystore - serverKeystoreMountPath: '/etc/cas/thekeystore' - # name of keystore file in container and in secret - serverKeystoreSubPath: 'thekeystore' - - ## Name of a Secret containing extra env vars - ## - extraEnvVarsSecret: '' - ## Choose which config files from casConfig to mount - casConfigMounts: - - 'application-standalone.properties' - - 'application-standalone.yml' - - 'log4j2.xml' - ## Create various config files from casConfig that may or may not be mounted - casConfig: - application-standalone.properties: |- - server.ssl.enabled=true - server.ssl.key-store=file:/etc/cas/thekeystore - server.ssl.key-store-type=PKCS12 - server.ssl.key-store-password=changeit - server.ssl.trust-store=file:/etc/cas/truststore - server.ssl.trust-store-type=PKCS12 - server.ssl.trust-store-password=changeit - - application-standalone.yml: |- - --- - cas: - server: - name: https://{{ .Values.casServerName }} - mgmt: - serverName: https://{{ .Values.casMgmtServerName }} - enableDiscoveryEndpointCall: false # turning off b/c external call to cas requires external DNS, need internal URL - inCommonMDQUrl: '' - - log4j2.xml: |- - - - - . - info - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -podAnnotations: {} - -## Pod security context -## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod -## -podSecurityContext: - enabled: true - fsGroup: 1000 - -containerSecurityContext: - enabled: false - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - runAsUser: 1000 - -## Override parts of this ingress in your own values file with appropriate host names -## This currently is only set up to work with Nginx Ingress Controller from Kubernetes project -cas: - service: - type: ClusterIP - port: 8443 - listenPortHttps: 8443 - listenPortJvmDebug: 5005 - ingress: - enabled: true - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/session-cookie-samesite: "None" - nginx.ingress.kubernetes.io/session-cookie-conditional-samesite-none: "true" - nginx.ingress.kubernetes.io/affinity: "cookie" - nginx.ingress.kubernetes.io/session-cookie-name: "sticky-session-route" - nginx.ingress.kubernetes.io/session-cookie-hash: "sha1" - nginx.ingress.kubernetes.io/secure-backends: "true" - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - hosts: - - host: cas.example.org - paths: - - "/cas" - - host: kubernetes.docker.internal - paths: - - "/cas" - tls: - - secretName: cas-server-ingress-tls - hosts: - - cas.example.org - - kubernetes.docker.internal - -## Override parts of this ingress in your own values file with appropriate host names -## This currently is only set up to work with Nginx Ingress Controller from Kubernetes project -bootadmin: - enabled: true - replicaCount: 1 - service: - type: ClusterIP - port: 8443 - listenPortHttps: 8444 - listenPortJvmDebug: 5005 - # Request some resources so kubernetes will schedule somewhere with enough resources - # Limits can also be set if desired - resources: - requests: - cpu: 50m - memory: 384Mi - # limits: - # cpu: 100m - # memory: 128Mi - nodeSelector: {} - tolerations: [] - affinity: {} - ingress: - enabled: true - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/session-cookie-samesite: "None" - nginx.ingress.kubernetes.io/session-cookie-conditional-samesite-none: "true" - nginx.ingress.kubernetes.io/affinity: "cookie" - nginx.ingress.kubernetes.io/session-cookie-name: "sticky-session-route" - nginx.ingress.kubernetes.io/session-cookie-hash: "sha1" - nginx.ingress.kubernetes.io/secure-backends: "true" - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - hosts: - - host: casadmin.example.org - paths: - - "/" - - host: kubernetes.docker.internal - paths: - - "/" - tls: - - secretName: cas-server-ingress-tls - hosts: - - casadmin.example.org - - kubernetes.docker.internal - -## Override parts of this ingress in your own values file with appropriate host names -## This currently is only set up to work with Nginx Ingress Controller from Kubernetes project -mgmt: - enabled: true - replicaCount: 1 - service: - type: ClusterIP - port: 8443 - listenPortHttps: 8443 - listenPortJvmDebug: 5005 - # Request some resources so kubernetes will schedule somewhere with enough resources - # Limits can also be set if desired - resources: - requests: - cpu: 50m - memory: 384Mi - # limits: - # cpu: 100m - # memory: 128Mi - nodeSelector: {} - tolerations: [] - affinity: {} - ingress: - enabled: true - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/session-cookie-samesite: "None" - nginx.ingress.kubernetes.io/session-cookie-conditional-samesite-none: "true" - nginx.ingress.kubernetes.io/affinity: "cookie" - nginx.ingress.kubernetes.io/session-cookie-name: "sticky-session-route" - nginx.ingress.kubernetes.io/session-cookie-hash: "sha1" - nginx.ingress.kubernetes.io/secure-backends: "true" - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - hosts: - - host: casmgmt.example.org - paths: - - "/cas-management" - - host: kubernetes.docker.internal - paths: - - "/cas-management" - tls: - - secretName: cas-server-ingress-tls - hosts: - - casmgmt.example.org - - kubernetes.docker.internal - -# Request some resources for main cas server so kubernetes will schedule somewhere with enough resources -# Limits can also be set if desired -resources: - requests: - cpu: 100m - memory: 512Mi -# limits: -# cpu: 100m -# memory: 128Mi - -# node selector for CAS server -nodeSelector: {} -# tolerations for CAS server (i.e taints on nodes that it can tolerate) -tolerations: [] -# affinity config for CAS server -affinity: {} - - -# credentials for boot admin server (ideally you should make boot admin accessible internally ) -bootAdminUser: 'casadmin' -bootAdminPassword: 'ChangeThisSecretPassword' - -# spring boot admin uses these credentials to hit actuators in cas (and it could be used externally) -casAdminUser: 'casuser' -casAdminPassword: 'Mellon' - -# rbac may or may not be necessary, but it can allow for certain types of discovery (e.g. tomcat cloud session replication) -rbac: - # specified whether RBAC resources should be created - create: true - -serviceAccount: - # Specifies whether a service account should be created - create: true - # Annotations to add to the service account - annotations: {} - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template - name: "" - - -## CAS can use a persistent volume to store config such as services and saml IDP/SP metadata that it pulls from git -## Enable persistence using Persistent Volume Claims -## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ -## -persistence: - ## If true, use a Persistent Volume Claim for data folder mounted where you specify using mountPath - ## - enabled: true - ## Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - ## Persistent Volume Claim annotations - ## - annotations: - ## Persistent Volume Access Mode - ## - accessModes: - - ReadWriteOnce - ## Persistent Volume size - ## - size: 2Gi - ## The path the volume will be mounted at, will contain writable folder called "data" under mountPath, - ## if volumePermissions init container creates it - ## - mountPath: /var/cas - -## Init containers parameters: -## volumePermissions: Change the owner and group of the persistent volume mountpoint to runAsUser:fsGroup values from -## the securityContext section. -## -volumePermissions: - enabled: false - image: - registry: docker.io - repository: alpine - tag: latest - pullPolicy: Always - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistryKeySecretName - ## Init container' resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - ## - resources: - # We usually recommend not to specify default resources and to leave this as a conscious - # choice for the user. This also increases chances charts run on environments with little - # resources, such as Minikube. If you do want to specify resources, uncomment the following - # lines, adjust them as necessary, and remove the curly braces after 'resources:'. - limits: {} - # cpu: 100m - # memory: 128Mi - requests: {} - # cpu: 100m - # memory: 128Mi - ## Init container Security Context - ## Note: the chown of the data folder is done to securityContext.runAsUser - ## and not the below volumePermissions.securityContext.runAsUser - ## When runAsUser is set to special value "auto", init container will try to chown the - ## data folder to autodetermined user&group, using commands: `id -u`:`id -G | cut -d" " -f2` - ## "auto" is especially useful for OpenShift which has scc with dynamic userids (and 0 is not allowed). - ## You may want to use this volumePermissions.securityContext.runAsUser="auto" in combination with - ## pod securityContext.enabled=false and shmVolume.chmod.enabled=false - ## - securityContext: - runAsUser: 0 diff --git a/apereo-cas/helm/create-cas-server-keystore-secret.sh b/apereo-cas/helm/create-cas-server-keystore-secret.sh deleted file mode 100644 index 374e9e4..0000000 --- a/apereo-cas/helm/create-cas-server-keystore-secret.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -# This script needs bash for pushd/popd -set -e -NAMESPACE=${1:-default} -KEYSTORE=../etc/cas/thekeystore - -# it's important that the service names are supported in the cert used for tomcat in cas-server and boot-admin -# keytool doesn't support wildcards which we really need to use here, e.g. *.cas-server.${NAMESPACE}.svc -# java wasn't resolving using all available dns suffixes so had to use [namespace].svc -SUBJECT=CN=cas.example.org,OU=Example,OU=Org,C=US -SAN=dns:cas.example.org,dns:casadmin.example.org,dns:cas-server-boot-admin,dns:cas-server-0.cas-server.${NAMESPACE}.svc,dns:cas-server-1.cas-server.${NAMESPACE}.svc - -if [ ! -f "$KEYSTORE" ] ; then - pushd .. - ./gradlew createKeyStore -PcertDir=./etc/cas -PcertificateDn="${SUBJECT}" -PcertificateSubAltName="${SAN}" - popd -fi - -kubectl delete secret cas-server-keystore --namespace "${NAMESPACE}" || true -kubectl create secret generic cas-server-keystore --namespace "${NAMESPACE}" --from-file=thekeystore=$KEYSTORE \ No newline at end of file diff --git a/apereo-cas/helm/create-ingress-tls.sh b/apereo-cas/helm/create-ingress-tls.sh deleted file mode 100644 index f2c5c7f..0000000 --- a/apereo-cas/helm/create-ingress-tls.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -NAMESPACE=${1:-default} -SUBJECT=/CN=cas.example.org/OU=Auth/O=example -SAN=DNS:casadmin.example.org,DNS:cas.example.org -SECRET_NAME=cas-server-ingress-tls -KEY_FILE=cas-ingress.key -CERT_FILE=cas-ingress.crt - -set -e - -# create certificate for external ingress -openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ - -keyout "${KEY_FILE}" -out ${CERT_FILE} -subj "${SUBJECT}" \ - -addext "subjectAltName = $SAN" - -kubectl delete secret "${SECRET_NAME}" --namespace "${NAMESPACE}" || true -# create tls secret with key and cert -kubectl create secret tls "${SECRET_NAME}" --namespace "${NAMESPACE}" --key "${KEY_FILE}" --cert "${CERT_FILE}" - diff --git a/apereo-cas/helm/create-truststore.sh b/apereo-cas/helm/create-truststore.sh deleted file mode 100644 index ebd01cf..0000000 --- a/apereo-cas/helm/create-truststore.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh -NAMESPACE=${1:-default} -INGRESS_CERT_FILE=cas-ingress.crt -CAS_CERT_FILE=cas.crt -CAS_KEYSTORE=../etc/cas/thekeystore -TRUST_STORE=../etc/cas/truststore -JAVA_CACERTS=${2:-/etc/ssl/certs/java/cacerts} - -STORE_PASS=changeit - -set -e - -if [ -f ${TRUST_STORE} ]; then - rm ${TRUST_STORE} -fi - -if [ -f "${JAVA_CACERTS}" ]; then - keytool -importkeystore -noprompt -srckeystore "${JAVA_CACERTS}" -srcstorepass "${STORE_PASS}" -destkeystore "${TRUST_STORE}" -deststoretype PKCS12 -deststorepass "${STORE_PASS}" -else - echo "Missing ${JAVA_CACERTS} JAVA_HOME is ${JAVA_HOME}" - if [ -d "${JAVA_HOME}" ]; then - find ${JAVA_HOME} -name cacerts -print - find ${JAVA_HOME} -name cacerts -exec keytool -importkeystore -noprompt -srckeystore {} -srcstorepass "${STORE_PASS}" -destkeystore "${TRUST_STORE}" -deststoretype PKCS12 -deststorepass "${STORE_PASS}" \; - fi -fi - -# create truststore that trusts ingress cert -if [ -f "${INGRESS_CERT_FILE}" ] ; then - keytool -importcert -noprompt -keystore "${TRUST_STORE}" -storepass "${STORE_PASS}" -alias cas-ingress -file "${INGRESS_CERT_FILE}" -storetype PKCS12 -else - echo "Missing ingress cert file to put in trust bundle: ${INGRESS_CERT_FILE}" -fi - -# add cas server cert to trust store -if [ -f "${CAS_KEYSTORE}" ] ; then - keytool -exportcert -keystore "${CAS_KEYSTORE}" -storepass "${STORE_PASS}" -alias cas -file "${CAS_CERT_FILE}" -rfc - keytool -importcert -noprompt -storepass "${STORE_PASS}" -keystore "${TRUST_STORE}" -alias cas -file "${CAS_CERT_FILE}" -storetype PKCS12 -else - echo "Missing keystore ${CAS_KEYSTORE} to put cas cert in trust bundle" -fi -kubectl delete configmap cas-truststore --namespace "${NAMESPACE}" || true -kubectl create configmap cas-truststore --namespace "${NAMESPACE}" --from-file=truststore=${TRUST_STORE} \ No newline at end of file diff --git a/apereo-cas/helm/delete-cas-server.sh b/apereo-cas/helm/delete-cas-server.sh deleted file mode 100644 index 3a8269d..0000000 --- a/apereo-cas/helm/delete-cas-server.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -NAMESPACE=${1:-default} -helm delete --namespace "${NAMESPACE}" cas-server \ No newline at end of file diff --git a/apereo-cas/helm/install-cas-server-example.sh b/apereo-cas/helm/install-cas-server-example.sh deleted file mode 100644 index 65ecde0..0000000 --- a/apereo-cas/helm/install-cas-server-example.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -NAMESPACE=${1:-default} -EXAMPLE=${2:-example1} - -helm upgrade --install cas-server --values values-${EXAMPLE}.yaml --namespace ${NAMESPACE} ./cas-server diff --git a/apereo-cas/helm/install-cas-server.sh b/apereo-cas/helm/install-cas-server.sh deleted file mode 100644 index 5e94d3a..0000000 --- a/apereo-cas/helm/install-cas-server.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -NAMESPACE=${1:-default} - -helm upgrade --install cas-server --namespace $NAMESPACE ./cas-server \ No newline at end of file diff --git a/apereo-cas/helm/values-example1.yaml b/apereo-cas/helm/values-example1.yaml deleted file mode 100644 index 53e2f73..0000000 --- a/apereo-cas/helm/values-example1.yaml +++ /dev/null @@ -1,63 +0,0 @@ ---- - -# This is example of a values file that can override and add to the default values.yaml -# Deployers might have one or more values files of their own per deployment environment. - -# CAS Server container properties -casServerContainer: - - # override profiles to include gitsvc - profiles: 'standalone,gitsvc' - - ## Override list of config files from casConfig to mount, include some from default values file - casConfigMounts: - - 'cas.properties' - - 'cas.yaml' - - 'application-gitsvc.yaml' - casConfig: - application-gitsvc.yaml: |- - --- - cas: - service-registry: - git: - repository-url: "{{- .Values.gitsvcRepoUrl -}}" - branches-to-clone: "{{- .Values.gitsvcBranchesToClone -}}" - active-branch: "{{- .Values.gitsvcActiveBranch -}}" - clone-directory: "{{- .Values.gitsvcCloneDirectory -}}" - root-directory: "{{- .Values.gitsvcRootDirectory -}}" - #eof - application-redis.yaml: |- - --- - #helm repo add bitnami https://charts.bitnami.com/bitnami - #helm install cas-server-redis bitnami/redis --set usePassword=false --set sentinel.enabled=true --set sentinel.usePassword=false - cas: - ticket: - registry: - redis: - enabled: true - database: 0 - host: 'cas-server-redis' - pool: - test-on-borrow: true - read-from: 'UPSTREAMPREFERRED' - crypto: - enabled: false - timeout: 5000 - port: 6379 - password: ' ' - cluster: - nodes: - - host: 'cas-server-redis-headless' - port: 6379 - password: ' ' - sentinel: - master: 'mymaster' - node: 'cas-server-redis-headless:26379' - # eof - - -gitsvcRepoUrl: 'https://github.com/apereo/cas.git' # need smaller repo with services -gitsvcBranchesToClone: 'master' -gitsvcActiveBranch: 'master' -gitsvcCloneDirectory: '/tmp/cas/services' -gitsvcRootDirectory: 'etc' # only supports one level diff --git a/apereo-cas/lombok.config b/apereo-cas/lombok.config deleted file mode 100644 index f562841..0000000 --- a/apereo-cas/lombok.config +++ /dev/null @@ -1,9 +0,0 @@ -lombok.log.fieldName = LOGGER -lombok.log.fieldIsStatic=true - -lombok.toString.doNotUseGetters=true -lombok.equalsAndHashCode.doNotUseGetters=true - -lombok.addLombokGeneratedAnnotation = true - -config.stopBubbling=true diff --git a/apereo-cas/settings.gradle b/apereo-cas/settings.gradle deleted file mode 100644 index 74901f4..0000000 --- a/apereo-cas/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'cas' diff --git a/apereo-cas/src/main/java/org/apereo/cas/config/CasOverlayOverrideConfiguration.java b/apereo-cas/src/main/java/org/apereo/cas/config/CasOverlayOverrideConfiguration.java deleted file mode 100644 index 3f89063..0000000 --- a/apereo-cas/src/main/java/org/apereo/cas/config/CasOverlayOverrideConfiguration.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.apereo.cas.config; - -//import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; - -//import org.apereo.cas.configuration.CasConfigurationProperties; - -@Configuration(value = "CasOverlayOverrideConfiguration", proxyBeanMethods = false) -//@EnableConfigurationProperties(CasConfigurationProperties.class) -public class CasOverlayOverrideConfiguration { - - /* - @Bean - public MyCustomBean myCustomBean() { - ... - } - */ -} diff --git a/apereo-cas/src/main/jib/docker/entrypoint.sh b/apereo-cas/src/main/jib/docker/entrypoint.sh deleted file mode 100644 index ccbdd1a..0000000 --- a/apereo-cas/src/main/jib/docker/entrypoint.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/sh - -ENTRYPOINT_DEBUG=${ENTRYPOINT_DEBUG:-false} -JVM_DEBUG=${JVM_DEBUG:-false} -JVM_DEBUG_PORT=${JVM_DEBUG_PORT:-5000} -JVM_DEBUG_SUSPEND=${JVM_DEBUG_SUSPEND:-n} -JVM_MEM_OPTS=${JVM_MEM_OPTS:--Xms512m -Xmx4096M} -JVM_EXTRA_OPTS=${JVM_EXTRA_OPTS:--server -noverify -XX:+TieredCompilation -XX:TieredStopAtLevel=1} - -if [ $JVM_DEBUG == "true" ]; then - JVM_EXTRA_OPTS="${JVM_EXTRA_OPTS} -Xdebug -Xrunjdwp:transport=dt_socket,address=*:${JVM_DEBUG_PORT},server=y,suspend=${JVM_DEBUG_SUSPEND}" -fi - -if [ $ENTRYPOINT_DEBUG == "true" ]; then - JVM_EXTRA_OPTS="${JVM_EXTRA_OPTS} -Ddebug=true" - - echo -e "\nChecking java..." - java -version - - if [ -d /etc/cas ] ; then - echo -e "\nListing CAS configuration under /etc/cas..." - ls -R /etc/cas - fi - echo -e "\nRemote debugger configured on port ${JVM_DEBUG_PORT} with suspend=${JVM_DEBUG_SUSPEND}: ${JVM_DEBUG}" - echo -e "\nJava args: ${JVM_MEM_OPTS} ${JVM_EXTRA_OPTS}" -fi - -echo -e "\nRunning CAS @ cas.war" -# shellcheck disable=SC2086 -exec java $JVM_EXTRA_OPTS $JVM_MEM_OPTS -jar cas.war "$@" diff --git a/apereo-cas/src/main/resources/META-INF/spring.factories b/apereo-cas/src/main/resources/META-INF/spring.factories deleted file mode 100644 index a2535d2..0000000 --- a/apereo-cas/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.apereo.cas.config.CasOverlayOverrideConfiguration diff --git a/apereo-cas/src/main/resources/application.yml b/apereo-cas/src/main/resources/application.yml deleted file mode 100644 index e532688..0000000 --- a/apereo-cas/src/main/resources/application.yml +++ /dev/null @@ -1,9 +0,0 @@ -# Application properties that need to be -# embedded within the web application can be included here -server: - cors: - allowed-origins: "*" # 允许所有域名 - allowed-methods: GET,POST # 允许的方法 - allowed-headers: "*" # 允许的头部 - allow-credentials: true # 是否允许凭证 - max-age: 3600 # 预检请求的缓存时间 diff --git a/apereo-cas/src/main/webapp/WEB-INF/web.xml b/apereo-cas/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 072a6a0..0000000 --- a/apereo-cas/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - \ No newline at end of file diff --git a/apereo-cas/system.properties b/apereo-cas/system.properties deleted file mode 100644 index 9146af5..0000000 --- a/apereo-cas/system.properties +++ /dev/null @@ -1 +0,0 @@ -java.runtime.version=11 diff --git a/controller/config.go b/controller/config.go index b76f1a7..586f8ee 100644 --- a/controller/config.go +++ b/controller/config.go @@ -13,6 +13,7 @@ type ConfigData struct { TokenEndpoint string `json:"token_endpoint"` UserinfoEndpoint string `json:"userinfo_endpoint"` DefaultScope string `json:"default_scope"` + DeviceAuthorization string `json:"device_authorization_endpoint"` } func getConfig(c *gin.Context) { @@ -22,6 +23,7 @@ func getConfig(c *gin.Context) { TokenEndpoint: g.Config().Endpoints.Token, UserinfoEndpoint: g.Config().Endpoints.Userinfo, DefaultScope: g.Config().DefaultScope, + DeviceAuthorization: g.Config().Endpoints.DeviceAuthorization, } c.JSON(http.StatusOK, handleSuccess(res)) } diff --git a/controller/oauth2_device_flow.go b/controller/oauth2_device_flow.go index c3fdf99..157d2bb 100644 --- a/controller/oauth2_device_flow.go +++ b/controller/oauth2_device_flow.go @@ -11,14 +11,15 @@ import ( ) type ReqDeviceData struct { - ClientId string `json:"client_id"` - Code string `json:"code"` - ResponseType string `json:"response_type"` - ExpiresIn int `json:"expires_in"` + ClientId string `json:"client_id"` + DeviceCode string `json:"device_code"` + GrantType string `json:"grant_type"` + ExpiresIn int `json:"expires_in"` } type ReqUserCodeData struct { - InitialAddress string `json:"initialAddress"` + ClientId string `json:"client_id"` + Scope string `json:"scope"` } func getUserCode(c *gin.Context) { @@ -27,10 +28,17 @@ func getUserCode(c *gin.Context) { c.JSON(http.StatusOK, handleError(err.Error())) return } + + clientId := reqData.ClientId + scope := reqData.Scope + method := "POST" - apiAddr := reqData.InitialAddress - body := fmt.Sprintf("") + apiAddr := g.Config().Endpoints.DeviceAuthorization + body := fmt.Sprintf("client_id=%s&scope=%s", clientId, scope) + header := make(map[string]string) + header["Content-Type"] = "application/x-www-form-urlencoded" + header["Content-Length"] = strconv.Itoa(len(body)) res, err := models.HandleRequest(method, apiAddr, g.UserAgent, body, g.Config().Timeout, header) if err != nil { @@ -50,11 +58,11 @@ func deviceFlow(c *gin.Context) { method := "POST" apiAddr := g.Config().Endpoints.Token - Code := reqData.Code + deviceCode := reqData.DeviceCode clientId := reqData.ClientId - ResponseType := reqData.ResponseType + grantType := reqData.GrantType ExpiresIn := reqData.ExpiresIn - body := fmt.Sprintf("code=%s&client_id=%s&response_type=%s", Code, clientId, ResponseType) + body := fmt.Sprintf("device_code=%s&client_id=%s&grant_type=%s", deviceCode, clientId, grantType) header := make(map[string]string) header["Content-Type"] = "application/x-www-form-urlencoded" diff --git a/controller/route.go b/controller/route.go index 3eb331a..eaae90b 100644 --- a/controller/route.go +++ b/controller/route.go @@ -20,8 +20,8 @@ func Routes(r *gin.Engine) { playground.Use(IPLimitCheck) playground.Use(NoCache()) playground.POST("/oauth2/pkce", pkce) - playground.POST("/oauth2/device_flow", deviceFlow) playground.POST("/oauth2/user_code", getUserCode) + playground.POST("/oauth2/device_flow", deviceFlow) playground.POST("/oauth2/client_credentials", clientCredentials) playground.POST("/oauth2/password", passwordMode) playground.POST("/oauth2/authorization_code", exchangeTokenByCode) diff --git a/front-standalone/src/views/playground/components/Device.vue b/front-standalone/src/views/playground/components/Device.vue index d458a05..e1d971f 100644 --- a/front-standalone/src/views/playground/components/Device.vue +++ b/front-standalone/src/views/playground/components/Device.vue @@ -17,6 +17,7 @@ const props = defineProps({ token_endpoint: "", userinfo_endpoint: "", default_scope: "", + device_authorization_endpoint: "", access_token_type: "", client_id: "", client_secret: "" @@ -83,10 +84,10 @@ function updateReqAndRes() { // Step 1 const activeName = ref('1'); const s1Data = reactive({ - token_endpoint: "", + device_authorization_endpoint: "", // redirect_uri: window.location.href.split("?")[0], scope: "", - response_type: "device_code", + // response_type: "device_code", // state: "", }); @@ -94,8 +95,7 @@ const initialAddress = ref(""); // 修改的同时拼接成url显示在Grant Url中 function handleS1Change() { - initialAddress.value = s1Data.token_endpoint.concat( - "?response_type=device_code", + initialAddress.value = s1Data.device_authorization_endpoint.concat( s1Data.scope?.length > 0 ? "&scope=".concat(s1Data.scope) : "", props.cfgData.client_id?.length > 0 ? "&client_id=".concat(props.cfgData.client_id) : "" ); @@ -118,12 +118,17 @@ function handleDeviceFlow() { lss.addItem(cs); } // window.location.href = initialAddress.value; + // const dataObject = { + // initialAddress: initialAddress.value + // }; const dataObject = { - initialAddress: initialAddress.value + client_id: props.cfgData.client_id, + scope: props.cfgData.default_scope }; fetchUserCode(dataObject).then(({code, msg, data}) => { if (code === 0) { const {request, response, rawjson, example} = data; + console.log(rawjson.user_code) if (rawjson.user_code === undefined || rawjson.user_code === '') return // const {interval, verification_uri, user_code, expires_in, device_code} = rawjson || {}; @@ -167,8 +172,8 @@ const qrCodeSrc = ref(""); function tokenAvailablelong(expire) { const dataObject = { client_id: props.cfgData.client_id, - code: device_code.value, - response_type: "device_code", + device_code: device_code.value, + grant_type: "urn:ietf:params:oauth:grant-type:device_code", expires_in: expire }; fetchACTokenByDevice(dataObject).then(({code, msg, data}) => { @@ -215,10 +220,6 @@ function handleRefreshToken() { if(props.cfgData.client_id.length === 0){ ElMessage.error('client_id is empty, please click the config button on the right side, and check the configuration'); return; - } - else if(props.cfgData.client_secret.length === 0){ - ElMessage.error('client_secret is empty, please click the config button on the right side, and check the configuration'); - return; }else if(currentRefreshToken.value.length === 0){ ElMessage.error('refresh_token is empty, please get the access_token firstly'); return; @@ -377,22 +378,17 @@ function deleteRow(index) { async function generateQRCode(url) { try { qrCodeSrc.value = await QRCode.toDataURL(url); - console.log() } catch (err) { console.error('Failed to generate QR code: ', err) } } watch(props.cfgData, (newValue) => { - s1Data.token_endpoint = newValue.token_endpoint; + s1Data.device_authorization_endpoint = newValue.device_authorization_endpoint; s1Data.scope = newValue.default_scope; - initialAddress.value = newValue.token_endpoint.concat( - "?response_type=device_code", + initialAddress.value = newValue.device_authorization_endpoint.concat( newValue.default_scope?.length > 0 ? "&scope=".concat(newValue.default_scope) : "", newValue.client_id?.length > 0 ? "&client_id=".concat(newValue.client_id) : "", - // "&redirect_uri=", - // s1Data.redirect_uri, - // s1Data.state?.length > 0 ? "&state=".concat(s1Data.state) : "" ); requestUri.value = newValue.userinfo_endpoint; s3TokenType.value = newValue.access_token_type; @@ -430,14 +426,12 @@ const handleDrag = (floatButton, container) => { Step 1: Request for Device Flow Authorization -

accessToken Endpoint

- - - +

Deivce Authorization Endpoint

+

Scope

- -

Response Type

- + +

Grant Url

diff --git a/front-standalone/src/views/playground/components/PKCE.vue b/front-standalone/src/views/playground/components/PKCE.vue index 4ae905b..79e6ef8 100644 --- a/front-standalone/src/views/playground/components/PKCE.vue +++ b/front-standalone/src/views/playground/components/PKCE.vue @@ -17,6 +17,7 @@ const props = defineProps({ token_endpoint: "", userinfo_endpoint: "", default_scope: "", + device_authorization_endpoint: "", access_token_type: "", client_id: "", client_secret: "" diff --git a/front-standalone/src/views/playground/index.vue b/front-standalone/src/views/playground/index.vue index 4ef218b..6cb480a 100644 --- a/front-standalone/src/views/playground/index.vue +++ b/front-standalone/src/views/playground/index.vue @@ -22,6 +22,7 @@ const configData = reactive({ token_endpoint: "", userinfo_endpoint: "", default_scope: "", + device_authorization_endpoint: "", access_token_type: "bearer", client_id: "", client_secret: "" @@ -41,6 +42,8 @@ async function getGlobalConfig() { Object.assign(configData, {client_id: id??"", client_secret: secret??""}); lss.removeItem("id"); lss.removeItem("secret"); + console.log('configData') + console.log(configData) } function handleSaveTokenType(){ @@ -103,6 +106,9 @@ onMounted(() => { {{ configData.authorization_endpoint }} + + {{ configData.device_authorization_endpoint }} + {{ configData.token_endpoint }} diff --git a/g/cfg.go b/g/cfg.go index d0b7ac1..0763ba3 100644 --- a/g/cfg.go +++ b/g/cfg.go @@ -31,9 +31,10 @@ type IpLimitConfig struct { EndpointConfig oauth endpoint 配置 */ type EndpointConfig struct { - Authorization string `json:"authorization"` - Token string `json:"token"` - Userinfo string `json:"userinfo"` + Authorization string `json:"authorization"` + Token string `json:"token"` + Userinfo string `json:"userinfo"` + DeviceAuthorization string `json:"device_authorization"` } /* diff --git a/go.mod b/go.mod index c5d1404..98b517d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/ECNU/Open-OAuth2Playground go 1.20 require ( - github.com/c-robinson/iplib v1.0.6 github.com/gin-gonic/gin v1.9.1 github.com/stretchr/testify v1.8.4 github.com/toolkits/file v0.0.0-20160325033739-a5b3c5147e07 diff --git a/go.sum b/go.sum index 7860af7..06e4bc9 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,6 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= -github.com/c-robinson/iplib v1.0.6 h1:FfZV9BWNrah3BgLCFl5/nDXe4RbOi/C9n+DeXFOv5CQ= -github.com/c-robinson/iplib v1.0.6/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= From 4d9f295d763b44c18a7db1a41f3a3c02293cbfcd Mon Sep 17 00:00:00 2001 From: Cheng Liang Date: Tue, 16 Jul 2024 22:58:44 +0800 Subject: [PATCH 2/2] add cfg-docker and environment in docker-compose.yml --- README.MD | 1 + cfg-docker.json | 26 ++++++++++++++++++++++++++ docker-compose.yml | 32 ++++++++++++++++++-------------- 3 files changed, 45 insertions(+), 14 deletions(-) create mode 100644 cfg-docker.json diff --git a/README.MD b/README.MD index 7c167c9..aa2eb85 100644 --- a/README.MD +++ b/README.MD @@ -183,6 +183,7 @@ cd /export/data sqlite3 cas.db ``` +[//]: # (todo:这部分要修改) - **cas的service** - authorization_code | client_credentials | device_flow模式: ```txt diff --git a/cfg-docker.json b/cfg-docker.json new file mode 100644 index 0000000..70b3be1 --- /dev/null +++ b/cfg-docker.json @@ -0,0 +1,26 @@ +{ + "logger": { + "dir": "logs/", + "level": "DEBUG", + "keepHours": 24 + }, + "endpoints": { + "authorization": "http://localhost/oauth2/authorize", + "token": "http://oauth-server-lite/oauth2/token", + "userinfo": "http://oauth-server-lite/oauth2/userinfo", + "device_authorization": "http://oauth-server-lite/oauth2/device/authorize" + }, + "iplimit": { + "enable": false, + "trust_ip": ["127.0.0.1", "::1"] + }, + "http": { + "route_base":"/", + "trust_proxy": ["127.0.0.1", "::1"], + "cors": ["http://127.0.0.1","http://localhost"], + "listen": "0.0.0.0:8080" + }, + "trust_domain": ["api.ecnu.edu.cn", "localhost", "oauth-server-lite"], + "default_scope": "Basic", + "timeout": 10 +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index abe3a90..17cb6db 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,24 +1,28 @@ version: '3' services: + redis: + image: redis:latest + container_name: oauth-redis + ports: + - "6379:6379" open-oauth2playground: - image: lacey620/open-oauth2playground:v1.0 + image: open-oauth2playground:v2.0 container_name: open-oauth2playground restart: always ports: - - "8080:80" + - "8085:8080" volumes: - - ./cfg.json:/app/Open-OAuth2Playground/cfg.json + - ./cfg-docker.json:/app/Open-OAuth2Playground/cfg.json command: ["/app/Open-OAuth2Playground/OAuth2Playground"] - cas-demo: - image: lacey620/cas-demo:v6.5.9 - container_name: cas-demo + oauth-server-lite: +# image: ecnunic/oauth-server-lite:v1.0 + image: lite-server-test:0716 + container_name: oauth-server-lite + depends_on: + - redis restart: always - ports: - - "8444:8444" environment: - - CAS_SERVER_NAME= - - SERVER_PORT= - volumes: - - ./cas_init_script.sh:/cas-overlay/cas_init_script.sh - entrypoint: ["/bin/bash", "-c"] - command: ["/cas-overlay/cas_init_script.sh && java -server -noverify -Xmx2048M -jar /cas-overlay/cas.war"] \ No newline at end of file + - CLIENT_HOST=127.0.0.1 + ports: + - "80:80" + - "8444:8444" \ No newline at end of file