├── .github └── FUNDING.yml ├── .gitignore ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── README.md ├── build-docker-images.sh ├── docker ├── cassandra │ └── Dockerfile └── vault │ └── config │ └── config.hcl ├── documentation ├── spring-cloud-vault-approle-cassandra.excalidraw ├── spring-cloud-vault-approle-cassandra.jpeg ├── spring-cloud-vault-approle-mysql.excalidraw ├── spring-cloud-vault-approle-mysql.jpeg ├── spring-vault-approle-multi-datasources-mysql.excalidraw ├── spring-vault-approle-multi-datasources-mysql.jpeg ├── spring-vault-approle-mysql.excalidraw └── spring-vault-approle-mysql.jpeg ├── init-environment.sh ├── mvnw ├── mvnw.cmd ├── pom.xml ├── remove-docker-images.sh ├── scripts ├── my-functions.sh ├── setup-spring-cloud-vault-approle-cassandra.sh ├── setup-spring-cloud-vault-approle-mysql.sh ├── setup-spring-vault-approle-multi-datasources-mysql.sh ├── setup-spring-vault-approle-mysql.sh └── unseal-vault-enable-approle-databases.sh ├── shutdown-environment.sh ├── spring-cloud-vault-approle-cassandra ├── README.md ├── book-service │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── ivanfranchin │ │ │ │ └── bookservice │ │ │ │ ├── BookServiceApplication.java │ │ │ │ ├── book │ │ │ │ ├── BookController.java │ │ │ │ ├── BookRepository.java │ │ │ │ ├── dto │ │ │ │ │ ├── BookResponse.java │ │ │ │ │ └── CreateBookRequest.java │ │ │ │ └── model │ │ │ │ │ └── Book.java │ │ │ │ └── config │ │ │ │ ├── CassandraConfig.java │ │ │ │ ├── SwaggerConfig.java │ │ │ │ └── VaultLeaseConfig.java │ │ └── resources │ │ │ ├── application.yml │ │ │ ├── banner.txt │ │ │ └── bootstrap.yml │ │ └── test │ │ └── java │ │ └── com │ │ └── ivanfranchin │ │ └── bookservice │ │ └── BookServiceApplicationTests.java └── simulate-get-books.sh ├── spring-cloud-vault-approle-mysql ├── README.md ├── simulate-get-students.sh └── student-service │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── ivanfranchin │ │ │ └── studentservice │ │ │ ├── StudentServiceApplication.java │ │ │ ├── config │ │ │ ├── SwaggerConfig.java │ │ │ └── VaultLeaseConfig.java │ │ │ └── student │ │ │ ├── StudentController.java │ │ │ ├── StudentRepository.java │ │ │ ├── dto │ │ │ ├── CreateStudentRequest.java │ │ │ └── StudentResponse.java │ │ │ └── model │ │ │ └── Student.java │ └── resources │ │ ├── application.yml │ │ ├── banner.txt │ │ └── bootstrap.yml │ └── test │ └── java │ └── com │ └── ivanfranchin │ └── studentservice │ └── StudentServiceApplicationTests.java ├── spring-vault-approle-multi-datasources-mysql ├── README.md ├── restaurant-service │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── ivanfranchin │ │ │ │ └── restaurantservice │ │ │ │ ├── RestaurantServiceApplication.java │ │ │ │ ├── config │ │ │ │ ├── SwaggerConfig.java │ │ │ │ └── VaultConfig.java │ │ │ │ ├── customer │ │ │ │ ├── CustomerController.java │ │ │ │ ├── CustomerRepository.java │ │ │ │ ├── config │ │ │ │ │ ├── CustomerDbConfig.java │ │ │ │ │ └── CustomerVaultLeaseConfig.java │ │ │ │ ├── dto │ │ │ │ │ ├── CreateCustomerRequest.java │ │ │ │ │ └── CustomerResponse.java │ │ │ │ └── model │ │ │ │ │ └── Customer.java │ │ │ │ └── dish │ │ │ │ ├── DishController.java │ │ │ │ ├── DishRepository.java │ │ │ │ ├── config │ │ │ │ ├── DishDbConfig.java │ │ │ │ └── DishVaultLeaseConfig.java │ │ │ │ ├── dto │ │ │ │ ├── CreateDishRequest.java │ │ │ │ └── DishResponse.java │ │ │ │ └── model │ │ │ │ └── Dish.java │ │ └── resources │ │ │ ├── application.yml │ │ │ └── banner.txt │ │ └── test │ │ └── java │ │ └── com │ │ └── ivanfranchin │ │ └── restaurantservice │ │ └── RestaurantServiceApplicationTests.java └── simulate-get-customers-dishes.sh └── spring-vault-approle-mysql ├── README.md ├── movie-service ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── ivanfranchin │ │ │ └── movieservice │ │ │ ├── MovieServiceApplication.java │ │ │ ├── config │ │ │ ├── DbConfig.java │ │ │ ├── SwaggerConfig.java │ │ │ ├── VaultConfig.java │ │ │ └── VaultLeaseConfig.java │ │ │ └── movie │ │ │ ├── MovieController.java │ │ │ ├── MovieRepository.java │ │ │ ├── dto │ │ │ ├── CreateMovieRequest.java │ │ │ └── MovieResponse.java │ │ │ └── model │ │ │ └── Movie.java │ └── resources │ │ ├── application.yml │ │ └── banner.txt │ └── test │ └── java │ └── com │ └── ivanfranchin │ └── movieservice │ └── MovieServiceApplicationTests.java └── simulate-get-movies.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ivangfr 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | 34 | ### MAC OS ### 35 | *.DS_store 36 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # springboot-vault-examples 2 | 3 | The goal of this project is to explore the capabilities of [`Vault`](https://www.vaultproject.io). To achieve this, we will develop applications that utilize `Vault` for storing and retrieving secrets. `Vault` dynamically generates credentials for accessing databases and relies on [`Consul`](https://www.consul.io) as the backend. The authentication method employed in `Vault` is `AppRole`. 4 | 5 | ## Proof-of-Concepts & Articles 6 | 7 | On [ivangfr.github.io](https://ivangfr.github.io), I have compiled my Proof-of-Concepts (PoCs) and articles. You can easily search for the technology you are interested in by using the filter. Who knows, perhaps I have already implemented a PoC or written an article about what you are looking for. 8 | 9 | ## Additional Readings 10 | 11 | - \[**Medium**\] [**Using HashiCorp Vault & Spring Cloud Vault to handle Spring Boot App Key/Value Secrets**](https://medium.com/@ivangfr/using-hashicorp-vault-spring-cloud-vault-to-handle-spring-boot-app-key-value-secrets-926b81d0173b) 12 | - \[**Medium**\] [**Using HashiCorp Vault & Spring Cloud Vault to obtain Dynamic MySQL Credentials**](https://medium.com/@ivangfr/using-hashicorp-vault-spring-cloud-vault-to-obtain-dynamic-mysql-credentials-5726f4fa53c2) 13 | - \[**Medium**\] [**How to Rotate Expired Spring Cloud Vault Relational DB Credentials Without Restarting the App**](https://medium.com/@ivangfr/how-to-rotate-expired-spring-cloud-vault-relational-db-credentials-without-restarting-the-app-66976fbb4bbe) 14 | 15 | ## Lease Rotation 16 | 17 | Many people encounter issues when using `Vault`, particularly with rotating the lease for backend databases. When a [`Spring Boot`](https://docs.spring.io/spring-boot/index.html) application requests a lease from `Vault` through the [`Spring Cloud Vault`](https://cloud.spring.io/spring-cloud-vault/reference/html/) library, the library **can automatically renew** the lease periodically (based on `default_lease_ttl`). 18 | 19 | However, once the maximum lease expiration time (`max_lease_ttl`) is reached, the lease cannot be renewed, and a new lease is needed. In this case, the `Spring Cloud Vault` library **cannot rotate** the lease, which may leave the application unable to connect to the database. 20 | 21 | To address this issue, we have developed solutions for applications using `Spring Cloud Vault` or [`Spring Vault`](https://docs.spring.io/spring-vault/reference/). Please see the examples below. 22 | 23 | ## Examples 24 | 25 | | Example | Diagram | 26 | |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------| 27 | | [spring-cloud-vault-approle-mysql](https://github.com/ivangfr/springboot-vault-examples/tree/master/spring-cloud-vault-approle-mysql) **(with lease rotation)** | ![project-diagram](documentation/spring-cloud-vault-approle-mysql.jpeg) | 28 | | [spring-cloud-vault-approle-cassandra](https://github.com/ivangfr/springboot-vault-examples/tree/master/spring-cloud-vault-approle-cassandra) | ![project-diagram](documentation/spring-cloud-vault-approle-cassandra.jpeg) | 29 | | [spring-vault-approle-mysql](https://github.com/ivangfr/springboot-vault-examples/tree/master/spring-vault-approle-mysql) **(with lease rotation)** | ![project-diagram](documentation/spring-vault-approle-mysql.jpeg) | 30 | | [spring-vault-approle-multi-datasources-mysql](https://github.com/ivangfr/springboot-vault-examples/tree/master/spring-vault-approle-multi-datasources-mysql) **(with lease rotation)** | ![project-diagram](documentation/spring-vault-approle-multi-datasources-mysql.jpeg) | 31 | 32 | ## Prerequisites 33 | 34 | - [`Java 21+`](https://www.oracle.com/java/technologies/downloads/#java21) 35 | - Some containerization tool [`Docker`](https://www.docker.com), [`Podman`](https://podman.io), etc. 36 | 37 | ## Initialize Environment 38 | 39 | Open a terminal and, inside the `springboot-vault-examples` root folder, run the following script: 40 | ``` 41 | ./init-environment.sh 42 | ``` 43 | 44 | This script will: 45 | - start `Consul`, `Vault`, `MySQL`, and `Cassandra` Docker containers; 46 | - unseal `Vault` and enable `AppRole` in it; 47 | - setup Database `roles` and `policies` in `Vault` for the application so that they can access their databases using dynamically generated credentials; 48 | - setup `KV Secrets` in `Vault` for the application; 49 | 50 | ## Shutdown Environment 51 | 52 | To shut down the environment, go to a terminal and, inside the `springboot-vault-examples` root folder, run the script below: 53 | ``` 54 | ./shutdown-environment.sh 55 | ``` 56 | 57 | ## Cleanup 58 | 59 | To remove all Docker images created by this project, go to a terminal and, inside the `springboot-vault-examples` root folder, run the following script: 60 | ``` 61 | ./remove-docker-images.sh all 62 | ``` 63 | -------------------------------------------------------------------------------- /build-docker-images.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source scripts/my-functions.sh 4 | 5 | check_script_input_parameter $1 6 | 7 | DOCKER_IMAGE_PREFIX="ivanfranchin" 8 | APP_VERSION="1.0.0" 9 | 10 | MOVIE_SERVICE_APP_NAME="movie-service" 11 | RESTAURANT_SERVICE_APP_NAME="restaurant-service" 12 | STUDENT_SERVICE_APP_NAME="student-service" 13 | BOOK_SERVICE_APP_NAME="book-service" 14 | 15 | MOVIE_SERVICE_PROJECT_NAME="spring-vault-approle-mysql/${MOVIE_SERVICE_APP_NAME}" 16 | RESTAURANT_SERVICE_PROJECT_NAME="spring-vault-approle-multi-datasources-mysql/${RESTAURANT_SERVICE_APP_NAME}" 17 | STUDENT_SERVICE_PROJECT_NAME="spring-cloud-vault-approle-mysql/${STUDENT_SERVICE_APP_NAME}" 18 | BOOK_SERVICE_PROJECT_NAME="spring-cloud-vault-approle-cassandra/${BOOK_SERVICE_APP_NAME}" 19 | 20 | MOVIE_SERVICE_DOCKER_IMAGE_NAME="${DOCKER_IMAGE_PREFIX}/${MOVIE_SERVICE_APP_NAME}:${APP_VERSION}" 21 | RESTAURANT_SERVICE_DOCKER_IMAGE_NAME="${DOCKER_IMAGE_PREFIX}/${RESTAURANT_SERVICE_APP_NAME}:${APP_VERSION}" 22 | STUDENT_SERVICE_DOCKER_IMAGE_NAME="${DOCKER_IMAGE_PREFIX}/${STUDENT_SERVICE_APP_NAME}:${APP_VERSION}" 23 | BOOK_SERVICE_DOCKER_IMAGE_NAME="${DOCKER_IMAGE_PREFIX}/${BOOK_SERVICE_APP_NAME}:${APP_VERSION}" 24 | 25 | SKIP_TESTS="true" 26 | 27 | if [ "$1" = "spring-vault-approle-mysql" ] || 28 | [ "$1" = "all" ]; 29 | then 30 | 31 | echo 32 | echo "--------------------------" 33 | echo "spring-vault-approle-mysql" 34 | echo "--------------------------" 35 | 36 | ./mvnw clean compile jib:dockerBuild \ 37 | --projects "$MOVIE_SERVICE_PROJECT_NAME" \ 38 | -DskipTests="$SKIP_TESTS" \ 39 | -Dimage="$MOVIE_SERVICE_DOCKER_IMAGE_NAME" 40 | 41 | fi 42 | 43 | if [ "$1" = "spring-vault-approle-multi-datasources-mysql" ] || 44 | [ "$1" = "all" ]; 45 | then 46 | 47 | echo 48 | echo "--------------------------------------------" 49 | echo "spring-vault-approle-multi-datasources-mysql" 50 | echo "--------------------------------------------" 51 | 52 | ./mvnw clean compile jib:dockerBuild \ 53 | --projects "$RESTAURANT_SERVICE_PROJECT_NAME" \ 54 | -DskipTests="$SKIP_TESTS" \ 55 | -Dimage="$RESTAURANT_SERVICE_DOCKER_IMAGE_NAME" 56 | 57 | fi 58 | 59 | if [ "$1" = "spring-cloud-vault-approle-mysql" ] || 60 | [ "$1" = "all" ]; 61 | then 62 | 63 | echo 64 | echo "--------------------------------" 65 | echo "spring-cloud-vault-approle-mysql" 66 | echo "--------------------------------" 67 | 68 | ./mvnw clean compile jib:dockerBuild \ 69 | --projects "$STUDENT_SERVICE_PROJECT_NAME" \ 70 | -DskipTests="$SKIP_TESTS" \ 71 | -Dimage="$STUDENT_SERVICE_DOCKER_IMAGE_NAME" 72 | 73 | fi 74 | 75 | if [ "$1" = "spring-cloud-vault-approle-cassandra" ] || 76 | [ "$1" = "all" ]; 77 | then 78 | 79 | echo 80 | echo "------------------------------------" 81 | echo "spring-cloud-vault-approle-cassandra" 82 | echo "------------------------------------" 83 | 84 | ./mvnw clean compile jib:dockerBuild \ 85 | --projects "$BOOK_SERVICE_PROJECT_NAME" \ 86 | -DskipTests="$SKIP_TESTS" \ 87 | -Dimage="$BOOK_SERVICE_DOCKER_IMAGE_NAME" 88 | 89 | fi 90 | -------------------------------------------------------------------------------- /docker/cassandra/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM cassandra:5.0.2 2 | 3 | RUN echo "authenticator: PasswordAuthenticator" >> /etc/cassandra/cassandra.yaml 4 | RUN echo "authorizer: org.apache.cassandra.auth.CassandraAuthorizer" >> /etc/cassandra/cassandra.yaml 5 | 6 | CMD ["cassandra", "-f"] -------------------------------------------------------------------------------- /docker/vault/config/config.hcl: -------------------------------------------------------------------------------- 1 | backend "consul" { 2 | address = "consul:8500" 3 | path = "vault/" 4 | } 5 | 6 | listener "tcp" { 7 | address = "0.0.0.0:8200" 8 | tls_disable = 1 9 | } 10 | 11 | max_lease_ttl = "5m" 12 | 13 | default_lease_ttl = "2m" -------------------------------------------------------------------------------- /documentation/spring-cloud-vault-approle-cassandra.excalidraw: -------------------------------------------------------------------------------- 1 | { 2 | "type": "excalidraw", 3 | "version": 2, 4 | "source": "https://excalidraw.com", 5 | "elements": [ 6 | { 7 | "type": "rectangle", 8 | "version": 1609, 9 | "versionNonce": 844524846, 10 | "isDeleted": false, 11 | "id": "pDQBF50DVQkNW6FOYiMA0", 12 | "fillStyle": "hachure", 13 | "strokeWidth": 1, 14 | "strokeStyle": "solid", 15 | "roughness": 1, 16 | "opacity": 100, 17 | "angle": 0, 18 | "x": 1712.9489147360075, 19 | "y": 528.709493637085, 20 | "strokeColor": "#000000", 21 | "backgroundColor": "#228be6", 22 | "width": 209, 23 | "height": 100, 24 | "seed": 647540718, 25 | "groupIds": [], 26 | "roundness": { 27 | "type": 3 28 | }, 29 | "boundElements": [ 30 | { 31 | "id": "G9iUtbLchkNIy8cE9efLt", 32 | "type": "arrow" 33 | }, 34 | { 35 | "id": "v_noVPCQLIYxK3vAcFidA", 36 | "type": "arrow" 37 | }, 38 | { 39 | "type": "text", 40 | "id": "xY49ZfOZ1MpCJsUAPvbLv" 41 | } 42 | ], 43 | "updated": 1679070325045, 44 | "link": null, 45 | "locked": false 46 | }, 47 | { 48 | "type": "text", 49 | "version": 1694, 50 | "versionNonce": 1760069550, 51 | "isDeleted": false, 52 | "id": "xY49ZfOZ1MpCJsUAPvbLv", 53 | "fillStyle": "hachure", 54 | "strokeWidth": 1, 55 | "strokeStyle": "solid", 56 | "roughness": 1, 57 | "opacity": 100, 58 | "angle": 0, 59 | "x": 1735.8989498312224, 60 | "y": 561.909493637085, 61 | "strokeColor": "#000000", 62 | "backgroundColor": "transparent", 63 | "width": 163.0999298095703, 64 | "height": 33.6, 65 | "seed": 813501938, 66 | "groupIds": [], 67 | "roundness": null, 68 | "boundElements": null, 69 | "updated": 1679070325045, 70 | "link": null, 71 | "locked": false, 72 | "fontSize": 28, 73 | "fontFamily": 1, 74 | "text": "book-service", 75 | "textAlign": "center", 76 | "verticalAlign": "middle", 77 | "containerId": "pDQBF50DVQkNW6FOYiMA0", 78 | "originalText": "book-service" 79 | }, 80 | { 81 | "type": "rectangle", 82 | "version": 1699, 83 | "versionNonce": 376751282, 84 | "isDeleted": false, 85 | "id": "-0FBflNeILiwzo-5Nu0Jm", 86 | "fillStyle": "hachure", 87 | "strokeWidth": 1, 88 | "strokeStyle": "solid", 89 | "roughness": 1, 90 | "opacity": 100, 91 | "angle": 0, 92 | "x": 1987.155159383845, 93 | "y": 528.8741359710693, 94 | "strokeColor": "#000000", 95 | "backgroundColor": "#12b886", 96 | "width": 209.18356323242188, 97 | "height": 99.67071533203125, 98 | "seed": 1813545518, 99 | "groupIds": [], 100 | "roundness": { 101 | "type": 3 102 | }, 103 | "boundElements": [ 104 | { 105 | "id": "G9iUtbLchkNIy8cE9efLt", 106 | "type": "arrow" 107 | }, 108 | { 109 | "type": "text", 110 | "id": "xdtZDght10Q-8WXWmy6af" 111 | } 112 | ], 113 | "updated": 1679070325045, 114 | "link": null, 115 | "locked": false 116 | }, 117 | { 118 | "type": "text", 119 | "version": 647, 120 | "versionNonce": 1310155246, 121 | "isDeleted": false, 122 | "id": "xdtZDght10Q-8WXWmy6af", 123 | "fillStyle": "hachure", 124 | "strokeWidth": 1, 125 | "strokeStyle": "solid", 126 | "roughness": 1, 127 | "opacity": 100, 128 | "angle": 0, 129 | "x": 2018.9889682827709, 130 | "y": 561.909493637085, 131 | "strokeColor": "#000000", 132 | "backgroundColor": "transparent", 133 | "width": 145.5159454345703, 134 | "height": 33.6, 135 | "seed": 1632272306, 136 | "groupIds": [], 137 | "roundness": null, 138 | "boundElements": null, 139 | "updated": 1679070325045, 140 | "link": null, 141 | "locked": false, 142 | "fontSize": 28, 143 | "fontFamily": 1, 144 | "text": "Cassandra", 145 | "textAlign": "center", 146 | "verticalAlign": "middle", 147 | "containerId": "-0FBflNeILiwzo-5Nu0Jm", 148 | "originalText": "Cassandra" 149 | }, 150 | { 151 | "type": "arrow", 152 | "version": 1487, 153 | "versionNonce": 2080106802, 154 | "isDeleted": false, 155 | "id": "G9iUtbLchkNIy8cE9efLt", 156 | "fillStyle": "solid", 157 | "strokeWidth": 1, 158 | "strokeStyle": "solid", 159 | "roughness": 1, 160 | "opacity": 100, 161 | "angle": 0, 162 | "x": 1929.3683014287014, 163 | "y": 579.0848041724885, 164 | "strokeColor": "#000000", 165 | "backgroundColor": "#ffffff", 166 | "width": 51.03813397137901, 167 | "height": 1.095574530317549, 168 | "seed": 241437806, 169 | "groupIds": [], 170 | "roundness": { 171 | "type": 2 172 | }, 173 | "boundElements": null, 174 | "updated": 1679070325045, 175 | "link": null, 176 | "locked": false, 177 | "startBinding": { 178 | "focus": -0.0009649396737168692, 179 | "gap": 7.419386692693934, 180 | "elementId": "pDQBF50DVQkNW6FOYiMA0" 181 | }, 182 | "endBinding": { 183 | "focus": 0.12131955462678062, 184 | "gap": 6.748723983764648, 185 | "elementId": "-0FBflNeILiwzo-5Nu0Jm" 186 | }, 187 | "lastCommittedPoint": null, 188 | "startArrowhead": null, 189 | "endArrowhead": null, 190 | "points": [ 191 | [ 192 | 0, 193 | 0 194 | ], 195 | [ 196 | 51.03813397137901, 197 | -1.095574530317549 198 | ] 199 | ] 200 | }, 201 | { 202 | "type": "rectangle", 203 | "version": 1819, 204 | "versionNonce": 1269602158, 205 | "isDeleted": false, 206 | "id": "hglstEROj-JOdWznNA4Xn", 207 | "fillStyle": "hachure", 208 | "strokeWidth": 1, 209 | "strokeStyle": "solid", 210 | "roughness": 1, 211 | "opacity": 100, 212 | "angle": 0, 213 | "x": 1137.6612773837332, 214 | "y": 528.8741359710693, 215 | "strokeColor": "#000000", 216 | "backgroundColor": "#be4bdb", 217 | "width": 209.18356323242188, 218 | "height": 99.67071533203125, 219 | "seed": 50575730, 220 | "groupIds": [], 221 | "roundness": { 222 | "type": 3 223 | }, 224 | "boundElements": [ 225 | { 226 | "id": "G9iUtbLchkNIy8cE9efLt", 227 | "type": "arrow" 228 | }, 229 | { 230 | "id": "9x_j6TL2dU9t9sf9cmxeF", 231 | "type": "arrow" 232 | }, 233 | { 234 | "type": "text", 235 | "id": "L8eB5nIR4OWIMNoPbG13g" 236 | } 237 | ], 238 | "updated": 1679070325045, 239 | "link": null, 240 | "locked": false 241 | }, 242 | { 243 | "type": "text", 244 | "version": 754, 245 | "versionNonce": 1425077294, 246 | "isDeleted": false, 247 | "id": "L8eB5nIR4OWIMNoPbG13g", 248 | "fillStyle": "hachure", 249 | "strokeWidth": 1, 250 | "strokeStyle": "solid", 251 | "roughness": 1, 252 | "opacity": 100, 253 | "angle": 0, 254 | "x": 1199.7070766391043, 255 | "y": 561.909493637085, 256 | "strokeColor": "#000000", 257 | "backgroundColor": "transparent", 258 | "width": 85.09196472167969, 259 | "height": 33.6, 260 | "seed": 1799638702, 261 | "groupIds": [], 262 | "roundness": null, 263 | "boundElements": null, 264 | "updated": 1679070325045, 265 | "link": null, 266 | "locked": false, 267 | "fontSize": 28, 268 | "fontFamily": 1, 269 | "text": "Consul", 270 | "textAlign": "center", 271 | "verticalAlign": "middle", 272 | "containerId": "hglstEROj-JOdWznNA4Xn", 273 | "originalText": "Consul" 274 | }, 275 | { 276 | "type": "rectangle", 277 | "version": 1913, 278 | "versionNonce": 77422194, 279 | "isDeleted": false, 280 | "id": "nqfK3cTJYGKykleenwbMV", 281 | "fillStyle": "hachure", 282 | "strokeWidth": 1, 283 | "strokeStyle": "solid", 284 | "roughness": 1, 285 | "opacity": 100, 286 | "angle": 0, 287 | "x": 1426.138236612249, 288 | "y": 528.8741359710693, 289 | "strokeColor": "#000000", 290 | "backgroundColor": "#fd7e14", 291 | "width": 209.18356323242188, 292 | "height": 99.67071533203125, 293 | "seed": 351255346, 294 | "groupIds": [], 295 | "roundness": { 296 | "type": 3 297 | }, 298 | "boundElements": [ 299 | { 300 | "id": "G9iUtbLchkNIy8cE9efLt", 301 | "type": "arrow" 302 | }, 303 | { 304 | "id": "9x_j6TL2dU9t9sf9cmxeF", 305 | "type": "arrow" 306 | }, 307 | { 308 | "id": "v_noVPCQLIYxK3vAcFidA", 309 | "type": "arrow" 310 | }, 311 | { 312 | "type": "text", 313 | "id": "jwb9aXQoztk9bcgGSWixa" 314 | } 315 | ], 316 | "updated": 1679070325045, 317 | "link": null, 318 | "locked": false 319 | }, 320 | { 321 | "type": "text", 322 | "version": 859, 323 | "versionNonce": 1734229614, 324 | "isDeleted": false, 325 | "id": "jwb9aXQoztk9bcgGSWixa", 326 | "fillStyle": "hachure", 327 | "strokeWidth": 1, 328 | "strokeStyle": "solid", 329 | "roughness": 1, 330 | "opacity": 100, 331 | "angle": 0, 332 | "x": 1494.610030740667, 333 | "y": 561.909493637085, 334 | "strokeColor": "#000000", 335 | "backgroundColor": "transparent", 336 | "width": 72.23997497558594, 337 | "height": 33.6, 338 | "seed": 1063405806, 339 | "groupIds": [], 340 | "roundness": null, 341 | "boundElements": null, 342 | "updated": 1679070325045, 343 | "link": null, 344 | "locked": false, 345 | "fontSize": 28, 346 | "fontFamily": 1, 347 | "text": "Vault", 348 | "textAlign": "center", 349 | "verticalAlign": "middle", 350 | "containerId": "nqfK3cTJYGKykleenwbMV", 351 | "originalText": "Vault" 352 | }, 353 | { 354 | "type": "arrow", 355 | "version": 794, 356 | "versionNonce": 1089993454, 357 | "isDeleted": false, 358 | "id": "9x_j6TL2dU9t9sf9cmxeF", 359 | "fillStyle": "hachure", 360 | "strokeWidth": 1, 361 | "strokeStyle": "solid", 362 | "roughness": 1, 363 | "opacity": 100, 364 | "angle": 0, 365 | "x": 1354.739112466741, 366 | "y": 579.3699159683388, 367 | "strokeColor": "#000000", 368 | "backgroundColor": "transparent", 369 | "width": 70.0482177734375, 370 | "height": 1.533813296576568, 371 | "seed": 360023282, 372 | "groupIds": [], 373 | "roundness": { 374 | "type": 2 375 | }, 376 | "boundElements": null, 377 | "updated": 1679070325045, 378 | "link": null, 379 | "locked": false, 380 | "startBinding": { 381 | "focus": -0.0027848701977440334, 382 | "gap": 7.8942718505859375, 383 | "elementId": "hglstEROj-JOdWznNA4Xn" 384 | }, 385 | "endBinding": { 386 | "focus": 0.11944880490231462, 387 | "gap": 1.3509063720704262, 388 | "elementId": "nqfK3cTJYGKykleenwbMV" 389 | }, 390 | "lastCommittedPoint": null, 391 | "startArrowhead": null, 392 | "endArrowhead": null, 393 | "points": [ 394 | [ 395 | 0, 396 | 0 397 | ], 398 | [ 399 | 70.0482177734375, 400 | -1.533813296576568 401 | ] 402 | ] 403 | }, 404 | { 405 | "type": "arrow", 406 | "version": 879, 407 | "versionNonce": 1724642034, 408 | "isDeleted": false, 409 | "id": "v_noVPCQLIYxK3vAcFidA", 410 | "fillStyle": "hachure", 411 | "strokeWidth": 1, 412 | "strokeStyle": "solid", 413 | "roughness": 1, 414 | "opacity": 100, 415 | "angle": 0, 416 | "x": 1640.8830085696466, 417 | "y": 578.0169479737217, 418 | "strokeColor": "#000000", 419 | "backgroundColor": "transparent", 420 | "width": 65.9580799093128, 421 | "height": 1.068881752072329, 422 | "seed": 1246165806, 423 | "groupIds": [], 424 | "roundness": { 425 | "type": 2 426 | }, 427 | "boundElements": null, 428 | "updated": 1679070325045, 429 | "link": null, 430 | "locked": false, 431 | "startBinding": { 432 | "focus": -0.13388681692850554, 433 | "gap": 5.5612087249757, 434 | "elementId": "nqfK3cTJYGKykleenwbMV" 435 | }, 436 | "endBinding": { 437 | "focus": 0.04358026736812336, 438 | "gap": 6.107826257048146, 439 | "elementId": "pDQBF50DVQkNW6FOYiMA0" 440 | }, 441 | "lastCommittedPoint": null, 442 | "startArrowhead": null, 443 | "endArrowhead": null, 444 | "points": [ 445 | [ 446 | 0, 447 | 0 448 | ], 449 | [ 450 | 65.9580799093128, 451 | 1.068881752072329 452 | ] 453 | ] 454 | }, 455 | { 456 | "type": "rectangle", 457 | "version": 586, 458 | "versionNonce": 625179054, 459 | "isDeleted": false, 460 | "id": "BIsis09k39tU6acryn_JZ", 461 | "fillStyle": "solid", 462 | "strokeWidth": 1, 463 | "strokeStyle": "solid", 464 | "roughness": 1, 465 | "opacity": 100, 466 | "angle": 0, 467 | "x": 1727.1716075839288, 468 | "y": 612.290506362915, 469 | "strokeColor": "#000000", 470 | "backgroundColor": "#ffffff", 471 | "width": 186, 472 | "height": 41, 473 | "seed": 505157298, 474 | "groupIds": [], 475 | "roundness": { 476 | "type": 3 477 | }, 478 | "boundElements": [ 479 | { 480 | "type": "text", 481 | "id": "O7MKXa9O5fn13zpYt3ae6" 482 | } 483 | ], 484 | "updated": 1679070325045, 485 | "link": null, 486 | "locked": false 487 | }, 488 | { 489 | "type": "text", 490 | "version": 427, 491 | "versionNonce": 1244151982, 492 | "isDeleted": false, 493 | "id": "O7MKXa9O5fn13zpYt3ae6", 494 | "fillStyle": "solid", 495 | "strokeWidth": 1, 496 | "strokeStyle": "solid", 497 | "roughness": 1, 498 | "opacity": 100, 499 | "angle": 0, 500 | "x": 1735.0017009677179, 501 | "y": 620.790506362915, 502 | "strokeColor": "#000000", 503 | "backgroundColor": "#ffffff", 504 | "width": 170.33981323242188, 505 | "height": 24, 506 | "seed": 1918592366, 507 | "groupIds": [], 508 | "roundness": null, 509 | "boundElements": null, 510 | "updated": 1679070325045, 511 | "link": null, 512 | "locked": false, 513 | "fontSize": 20, 514 | "fontFamily": 1, 515 | "text": "spring-cloud-vault", 516 | "textAlign": "center", 517 | "verticalAlign": "middle", 518 | "containerId": "BIsis09k39tU6acryn_JZ", 519 | "originalText": "spring-cloud-vault" 520 | } 521 | ], 522 | "appState": { 523 | "gridSize": null, 524 | "viewBackgroundColor": "#ffffff" 525 | }, 526 | "files": {} 527 | } -------------------------------------------------------------------------------- /documentation/spring-cloud-vault-approle-cassandra.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivangfr/springboot-vault-examples/265973cdfe525bfb78630ca5eebafbf655bbfbad/documentation/spring-cloud-vault-approle-cassandra.jpeg -------------------------------------------------------------------------------- /documentation/spring-cloud-vault-approle-mysql.excalidraw: -------------------------------------------------------------------------------- 1 | { 2 | "type": "excalidraw", 3 | "version": 2, 4 | "source": "https://excalidraw.com", 5 | "elements": [ 6 | { 7 | "type": "rectangle", 8 | "version": 1527, 9 | "versionNonce": 439181106, 10 | "isDeleted": false, 11 | "id": "7PyvqfqEfgIIL8m3Kui9M", 12 | "fillStyle": "hachure", 13 | "strokeWidth": 1, 14 | "strokeStyle": "solid", 15 | "roughness": 1, 16 | "opacity": 100, 17 | "angle": 0, 18 | "x": 1890.6482860738984, 19 | "y": 641.6432552337646, 20 | "strokeColor": "#000000", 21 | "backgroundColor": "#228be6", 22 | "width": 209, 23 | "height": 100, 24 | "seed": 137541746, 25 | "groupIds": [], 26 | "roundness": { 27 | "type": 3 28 | }, 29 | "boundElements": [ 30 | { 31 | "id": "06RBJoJrR39OFKGXiWXE9", 32 | "type": "arrow" 33 | }, 34 | { 35 | "id": "gC-a3RXz6QXDlE-RntVlz", 36 | "type": "arrow" 37 | }, 38 | { 39 | "type": "text", 40 | "id": "BHAKZPGJU30vfJilidnZn" 41 | } 42 | ], 43 | "updated": 1679070269015, 44 | "link": null, 45 | "locked": false 46 | }, 47 | { 48 | "type": "text", 49 | "version": 1607, 50 | "versionNonce": 977469806, 51 | "isDeleted": false, 52 | "id": "BHAKZPGJU30vfJilidnZn", 53 | "fillStyle": "hachure", 54 | "strokeWidth": 1, 55 | "strokeStyle": "solid", 56 | "roughness": 1, 57 | "opacity": 100, 58 | "angle": 0, 59 | "x": 1935.8583156759491, 60 | "y": 658.0432552337646, 61 | "strokeColor": "#000000", 62 | "backgroundColor": "transparent", 63 | "width": 118.57994079589844, 64 | "height": 67.2, 65 | "seed": 1405665198, 66 | "groupIds": [], 67 | "roundness": null, 68 | "boundElements": null, 69 | "updated": 1679070269016, 70 | "link": null, 71 | "locked": false, 72 | "fontSize": 28, 73 | "fontFamily": 1, 74 | "text": "student-\nservice", 75 | "textAlign": "center", 76 | "verticalAlign": "middle", 77 | "containerId": "7PyvqfqEfgIIL8m3Kui9M", 78 | "originalText": "student-\nservice" 79 | }, 80 | { 81 | "type": "rectangle", 82 | "version": 1645, 83 | "versionNonce": 1900078318, 84 | "isDeleted": false, 85 | "id": "AxGbflQc1o9Jlfz-iBqoo", 86 | "fillStyle": "hachure", 87 | "strokeWidth": 1, 88 | "strokeStyle": "solid", 89 | "roughness": 1, 90 | "opacity": 100, 91 | "angle": 0, 92 | "x": 2165.4557880459543, 93 | "y": 641.807897567749, 94 | "strokeColor": "#000000", 95 | "backgroundColor": "#82c91e", 96 | "width": 209.18356323242188, 97 | "height": 99.67071533203125, 98 | "seed": 971430450, 99 | "groupIds": [], 100 | "roundness": { 101 | "type": 3 102 | }, 103 | "boundElements": [ 104 | { 105 | "id": "06RBJoJrR39OFKGXiWXE9", 106 | "type": "arrow" 107 | }, 108 | { 109 | "type": "text", 110 | "id": "fp4HX3-eDqI1Pk8bYXf27" 111 | } 112 | ], 113 | "updated": 1679070269015, 114 | "link": null, 115 | "locked": false 116 | }, 117 | { 118 | "type": "text", 119 | "version": 586, 120 | "versionNonce": 712877682, 121 | "isDeleted": false, 122 | "id": "fp4HX3-eDqI1Pk8bYXf27", 123 | "fillStyle": "hachure", 124 | "strokeWidth": 1, 125 | "strokeStyle": "solid", 126 | "roughness": 1, 127 | "opacity": 100, 128 | "angle": 0, 129 | "x": 2225.989593893122, 130 | "y": 674.8432552337647, 131 | "strokeColor": "#000000", 132 | "backgroundColor": "transparent", 133 | "width": 88.11595153808594, 134 | "height": 33.6, 135 | "seed": 1613797870, 136 | "groupIds": [], 137 | "roundness": null, 138 | "boundElements": null, 139 | "updated": 1679070269015, 140 | "link": null, 141 | "locked": false, 142 | "fontSize": 28, 143 | "fontFamily": 1, 144 | "text": "MySQL", 145 | "textAlign": "center", 146 | "verticalAlign": "middle", 147 | "containerId": "AxGbflQc1o9Jlfz-iBqoo", 148 | "originalText": "MySQL" 149 | }, 150 | { 151 | "type": "arrow", 152 | "version": 1304, 153 | "versionNonce": 1811523694, 154 | "isDeleted": false, 155 | "id": "06RBJoJrR39OFKGXiWXE9", 156 | "fillStyle": "solid", 157 | "strokeWidth": 1, 158 | "strokeStyle": "solid", 159 | "roughness": 1, 160 | "opacity": 100, 161 | "angle": 0, 162 | "x": 2107.0676727665923, 163 | "y": 692.4734956836612, 164 | "strokeColor": "#000000", 165 | "backgroundColor": "#ffffff", 166 | "width": 51.63939129559776, 167 | "height": 1.5946892228545266, 168 | "seed": 1815135218, 169 | "groupIds": [], 170 | "roundness": { 171 | "type": 2 172 | }, 173 | "boundElements": null, 174 | "updated": 1679070269015, 175 | "link": null, 176 | "locked": false, 177 | "startBinding": { 178 | "focus": -0.000964939673718254, 179 | "gap": 7.419386692693706, 180 | "elementId": "7PyvqfqEfgIIL8m3Kui9M" 181 | }, 182 | "endBinding": { 183 | "focus": 0.12131955462677636, 184 | "gap": 6.748723983764648, 185 | "elementId": "AxGbflQc1o9Jlfz-iBqoo" 186 | }, 187 | "lastCommittedPoint": null, 188 | "startArrowhead": null, 189 | "endArrowhead": null, 190 | "points": [ 191 | [ 192 | 0, 193 | 0 194 | ], 195 | [ 196 | 51.63939129559776, 197 | -1.5946892228545266 198 | ] 199 | ] 200 | }, 201 | { 202 | "type": "rectangle", 203 | "version": 1737, 204 | "versionNonce": 1801365746, 205 | "isDeleted": false, 206 | "id": "F9tRZTboXATfsHgKGYnCs", 207 | "fillStyle": "hachure", 208 | "strokeWidth": 1, 209 | "strokeStyle": "solid", 210 | "roughness": 1, 211 | "opacity": 100, 212 | "angle": 0, 213 | "x": 1315.3606487216239, 214 | "y": 641.807897567749, 215 | "strokeColor": "#000000", 216 | "backgroundColor": "#be4bdb", 217 | "width": 209.18356323242188, 218 | "height": 99.67071533203125, 219 | "seed": 1059269678, 220 | "groupIds": [], 221 | "roundness": { 222 | "type": 3 223 | }, 224 | "boundElements": [ 225 | { 226 | "id": "06RBJoJrR39OFKGXiWXE9", 227 | "type": "arrow" 228 | }, 229 | { 230 | "id": "AX9kZYkC0g8Z7p1uPUlzn", 231 | "type": "arrow" 232 | }, 233 | { 234 | "type": "text", 235 | "id": "x25YUFyARbd6QuHI6UdHn" 236 | } 237 | ], 238 | "updated": 1679070269015, 239 | "link": null, 240 | "locked": false 241 | }, 242 | { 243 | "type": "text", 244 | "version": 673, 245 | "versionNonce": 1889798194, 246 | "isDeleted": false, 247 | "id": "x25YUFyARbd6QuHI6UdHn", 248 | "fillStyle": "hachure", 249 | "strokeWidth": 1, 250 | "strokeStyle": "solid", 251 | "roughness": 1, 252 | "opacity": 100, 253 | "angle": 0, 254 | "x": 1377.406447976995, 255 | "y": 674.8432552337647, 256 | "strokeColor": "#000000", 257 | "backgroundColor": "transparent", 258 | "width": 85.09196472167969, 259 | "height": 33.6, 260 | "seed": 420967858, 261 | "groupIds": [], 262 | "roundness": null, 263 | "boundElements": null, 264 | "updated": 1679070269015, 265 | "link": null, 266 | "locked": false, 267 | "fontSize": 28, 268 | "fontFamily": 1, 269 | "text": "Consul", 270 | "textAlign": "center", 271 | "verticalAlign": "middle", 272 | "containerId": "F9tRZTboXATfsHgKGYnCs", 273 | "originalText": "Consul" 274 | }, 275 | { 276 | "type": "rectangle", 277 | "version": 1831, 278 | "versionNonce": 2113017646, 279 | "isDeleted": false, 280 | "id": "nAEBAWdLij6VIUfOemq05", 281 | "fillStyle": "hachure", 282 | "strokeWidth": 1, 283 | "strokeStyle": "solid", 284 | "roughness": 1, 285 | "opacity": 100, 286 | "angle": 0, 287 | "x": 1603.8376079501395, 288 | "y": 641.807897567749, 289 | "strokeColor": "#000000", 290 | "backgroundColor": "#fd7e14", 291 | "width": 209.18356323242188, 292 | "height": 99.67071533203125, 293 | "seed": 1049196142, 294 | "groupIds": [], 295 | "roundness": { 296 | "type": 3 297 | }, 298 | "boundElements": [ 299 | { 300 | "id": "06RBJoJrR39OFKGXiWXE9", 301 | "type": "arrow" 302 | }, 303 | { 304 | "id": "AX9kZYkC0g8Z7p1uPUlzn", 305 | "type": "arrow" 306 | }, 307 | { 308 | "id": "gC-a3RXz6QXDlE-RntVlz", 309 | "type": "arrow" 310 | }, 311 | { 312 | "type": "text", 313 | "id": "rm1LEkmBV_o-V1IQwpUQ7" 314 | } 315 | ], 316 | "updated": 1679070269015, 317 | "link": null, 318 | "locked": false 319 | }, 320 | { 321 | "type": "text", 322 | "version": 778, 323 | "versionNonce": 1863856626, 324 | "isDeleted": false, 325 | "id": "rm1LEkmBV_o-V1IQwpUQ7", 326 | "fillStyle": "hachure", 327 | "strokeWidth": 1, 328 | "strokeStyle": "solid", 329 | "roughness": 1, 330 | "opacity": 100, 331 | "angle": 0, 332 | "x": 1672.3094020785575, 333 | "y": 674.8432552337647, 334 | "strokeColor": "#000000", 335 | "backgroundColor": "transparent", 336 | "width": 72.23997497558594, 337 | "height": 33.6, 338 | "seed": 1640099698, 339 | "groupIds": [], 340 | "roundness": null, 341 | "boundElements": null, 342 | "updated": 1679070269015, 343 | "link": null, 344 | "locked": false, 345 | "fontSize": 28, 346 | "fontFamily": 1, 347 | "text": "Vault", 348 | "textAlign": "center", 349 | "verticalAlign": "middle", 350 | "containerId": "nAEBAWdLij6VIUfOemq05", 351 | "originalText": "Vault" 352 | }, 353 | { 354 | "type": "arrow", 355 | "version": 553, 356 | "versionNonce": 673570162, 357 | "isDeleted": false, 358 | "id": "AX9kZYkC0g8Z7p1uPUlzn", 359 | "fillStyle": "hachure", 360 | "strokeWidth": 1, 361 | "strokeStyle": "solid", 362 | "roughness": 1, 363 | "opacity": 100, 364 | "angle": 0, 365 | "x": 1532.4384838046317, 366 | "y": 692.3521144859872, 367 | "strokeColor": "#000000", 368 | "backgroundColor": "transparent", 369 | "width": 70.0482177734375, 370 | "height": 1.533813296576568, 371 | "seed": 120354990, 372 | "groupIds": [], 373 | "roundness": { 374 | "type": 2 375 | }, 376 | "boundElements": null, 377 | "updated": 1679070269015, 378 | "link": null, 379 | "locked": false, 380 | "startBinding": { 381 | "focus": -0.0027848701977451233, 382 | "gap": 7.8942718505859375, 383 | "elementId": "F9tRZTboXATfsHgKGYnCs" 384 | }, 385 | "endBinding": { 386 | "focus": 0.1194488049023157, 387 | "gap": 1.3509063720704262, 388 | "elementId": "nAEBAWdLij6VIUfOemq05" 389 | }, 390 | "lastCommittedPoint": null, 391 | "startArrowhead": null, 392 | "endArrowhead": null, 393 | "points": [ 394 | [ 395 | 0, 396 | 0 397 | ], 398 | [ 399 | 70.0482177734375, 400 | -1.533813296576568 401 | ] 402 | ] 403 | }, 404 | { 405 | "type": "arrow", 406 | "version": 638, 407 | "versionNonce": 1002859182, 408 | "isDeleted": false, 409 | "id": "gC-a3RXz6QXDlE-RntVlz", 410 | "fillStyle": "hachure", 411 | "strokeWidth": 1, 412 | "strokeStyle": "solid", 413 | "roughness": 1, 414 | "opacity": 100, 415 | "angle": 0, 416 | "x": 1818.582379907537, 417 | "y": 691.5601763384554, 418 | "strokeColor": "#000000", 419 | "backgroundColor": "transparent", 420 | "width": 65.9580799093128, 421 | "height": 1.068881752072329, 422 | "seed": 712454450, 423 | "groupIds": [], 424 | "roundness": { 425 | "type": 2 426 | }, 427 | "boundElements": null, 428 | "updated": 1679070269015, 429 | "link": null, 430 | "locked": false, 431 | "startBinding": { 432 | "focus": -0.13388681692850443, 433 | "gap": 5.561208724975586, 434 | "elementId": "nAEBAWdLij6VIUfOemq05" 435 | }, 436 | "endBinding": { 437 | "focus": 0.04358026736812219, 438 | "gap": 6.107826257048373, 439 | "elementId": "7PyvqfqEfgIIL8m3Kui9M" 440 | }, 441 | "lastCommittedPoint": null, 442 | "startArrowhead": null, 443 | "endArrowhead": null, 444 | "points": [ 445 | [ 446 | 0, 447 | 0 448 | ], 449 | [ 450 | 65.9580799093128, 451 | 1.068881752072329 452 | ] 453 | ] 454 | }, 455 | { 456 | "type": "rectangle", 457 | "version": 504, 458 | "versionNonce": 591444658, 459 | "isDeleted": false, 460 | "id": "K7AJb3BSRzDejUn4W4Q6J", 461 | "fillStyle": "solid", 462 | "strokeWidth": 1, 463 | "strokeStyle": "solid", 464 | "roughness": 1, 465 | "opacity": 100, 466 | "angle": 0, 467 | "x": 1904.8709789218192, 468 | "y": 725.3567447662354, 469 | "strokeColor": "#000000", 470 | "backgroundColor": "#ffffff", 471 | "width": 186, 472 | "height": 41, 473 | "seed": 1085616878, 474 | "groupIds": [], 475 | "roundness": { 476 | "type": 3 477 | }, 478 | "boundElements": [ 479 | { 480 | "type": "text", 481 | "id": "faqaR8z3RcAx2zNMxWD0Z" 482 | } 483 | ], 484 | "updated": 1679070269015, 485 | "link": null, 486 | "locked": false 487 | }, 488 | { 489 | "type": "text", 490 | "version": 346, 491 | "versionNonce": 1759585202, 492 | "isDeleted": false, 493 | "id": "faqaR8z3RcAx2zNMxWD0Z", 494 | "fillStyle": "solid", 495 | "strokeWidth": 1, 496 | "strokeStyle": "solid", 497 | "roughness": 1, 498 | "opacity": 100, 499 | "angle": 0, 500 | "x": 1912.7010723056082, 501 | "y": 733.8567447662354, 502 | "strokeColor": "#000000", 503 | "backgroundColor": "#ffffff", 504 | "width": 170.33981323242188, 505 | "height": 24, 506 | "seed": 916339442, 507 | "groupIds": [], 508 | "roundness": null, 509 | "boundElements": null, 510 | "updated": 1679070269015, 511 | "link": null, 512 | "locked": false, 513 | "fontSize": 20, 514 | "fontFamily": 1, 515 | "text": "spring-cloud-vault", 516 | "textAlign": "center", 517 | "verticalAlign": "middle", 518 | "containerId": "K7AJb3BSRzDejUn4W4Q6J", 519 | "originalText": "spring-cloud-vault" 520 | } 521 | ], 522 | "appState": { 523 | "gridSize": null, 524 | "viewBackgroundColor": "#ffffff" 525 | }, 526 | "files": {} 527 | } -------------------------------------------------------------------------------- /documentation/spring-cloud-vault-approle-mysql.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivangfr/springboot-vault-examples/265973cdfe525bfb78630ca5eebafbf655bbfbad/documentation/spring-cloud-vault-approle-mysql.jpeg -------------------------------------------------------------------------------- /documentation/spring-vault-approle-multi-datasources-mysql.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivangfr/springboot-vault-examples/265973cdfe525bfb78630ca5eebafbf655bbfbad/documentation/spring-vault-approle-multi-datasources-mysql.jpeg -------------------------------------------------------------------------------- /documentation/spring-vault-approle-mysql.excalidraw: -------------------------------------------------------------------------------- 1 | { 2 | "type": "excalidraw", 3 | "version": 2, 4 | "source": "https://excalidraw.com", 5 | "elements": [ 6 | { 7 | "type": "rectangle", 8 | "version": 1381, 9 | "versionNonce": 1291936302, 10 | "isDeleted": false, 11 | "id": "KXKAfr36NEVJaKqVeM5IU", 12 | "fillStyle": "hachure", 13 | "strokeWidth": 1, 14 | "strokeStyle": "solid", 15 | "roughness": 1, 16 | "opacity": 100, 17 | "angle": 0, 18 | "x": 1375.8871471578825, 19 | "y": 476.343641281128, 20 | "strokeColor": "#000000", 21 | "backgroundColor": "#228be6", 22 | "width": 209, 23 | "height": 100, 24 | "seed": 1377158382, 25 | "groupIds": [], 26 | "roundness": { 27 | "type": 3 28 | }, 29 | "boundElements": [ 30 | { 31 | "id": "QwHxD7L0DzHR8TS7cA3aL", 32 | "type": "arrow" 33 | }, 34 | { 35 | "id": "4l95zQSRvT8QpQQN3H11Q", 36 | "type": "arrow" 37 | }, 38 | { 39 | "type": "text", 40 | "id": "Vquiw9VCb7uDTl8JgJYiK" 41 | } 42 | ], 43 | "updated": 1679070080414, 44 | "link": null, 45 | "locked": false 46 | }, 47 | { 48 | "type": "text", 49 | "version": 1448, 50 | "versionNonce": 817683250, 51 | "isDeleted": false, 52 | "id": "Vquiw9VCb7uDTl8JgJYiK", 53 | "fillStyle": "hachure", 54 | "strokeWidth": 1, 55 | "strokeStyle": "solid", 56 | "roughness": 1, 57 | "opacity": 100, 58 | "angle": 0, 59 | "x": 1393.615182131027, 60 | "y": 509.543641281128, 61 | "strokeColor": "#000000", 62 | "backgroundColor": "transparent", 63 | "width": 173.54393005371094, 64 | "height": 33.6, 65 | "seed": 1455933682, 66 | "groupIds": [], 67 | "roundness": null, 68 | "boundElements": null, 69 | "updated": 1679070080417, 70 | "link": null, 71 | "locked": false, 72 | "fontSize": 28, 73 | "fontFamily": 1, 74 | "text": "movie-service", 75 | "textAlign": "center", 76 | "verticalAlign": "middle", 77 | "containerId": "KXKAfr36NEVJaKqVeM5IU", 78 | "originalText": "movie-service" 79 | }, 80 | { 81 | "type": "rectangle", 82 | "version": 1469, 83 | "versionNonce": 132095922, 84 | "isDeleted": false, 85 | "id": "zoIZ5IbBrJtOIpJcanTSk", 86 | "fillStyle": "hachure", 87 | "strokeWidth": 1, 88 | "strokeStyle": "solid", 89 | "roughness": 1, 90 | "opacity": 100, 91 | "angle": 0, 92 | "x": 1650.21692696197, 93 | "y": 476.50828361511236, 94 | "strokeColor": "#000000", 95 | "backgroundColor": "#82c91e", 96 | "width": 209.18356323242188, 97 | "height": 99.67071533203125, 98 | "seed": 824899374, 99 | "groupIds": [], 100 | "roundness": { 101 | "type": 3 102 | }, 103 | "boundElements": [ 104 | { 105 | "id": "QwHxD7L0DzHR8TS7cA3aL", 106 | "type": "arrow" 107 | }, 108 | { 109 | "type": "text", 110 | "id": "_-dIm6fjbnP-K99d4QVIA" 111 | } 112 | ], 113 | "updated": 1679070080414, 114 | "link": null, 115 | "locked": false 116 | }, 117 | { 118 | "type": "text", 119 | "version": 412, 120 | "versionNonce": 1786194158, 121 | "isDeleted": false, 122 | "id": "_-dIm6fjbnP-K99d4QVIA", 123 | "fillStyle": "hachure", 124 | "strokeWidth": 1, 125 | "strokeStyle": "solid", 126 | "roughness": 1, 127 | "opacity": 100, 128 | "angle": 0, 129 | "x": 1710.750732809138, 130 | "y": 509.543641281128, 131 | "strokeColor": "#000000", 132 | "backgroundColor": "transparent", 133 | "width": 88.11595153808594, 134 | "height": 33.6, 135 | "seed": 75881138, 136 | "groupIds": [], 137 | "roundness": null, 138 | "boundElements": null, 139 | "updated": 1679070080418, 140 | "link": null, 141 | "locked": false, 142 | "fontSize": 28, 143 | "fontFamily": 1, 144 | "text": "MySQL", 145 | "textAlign": "center", 146 | "verticalAlign": "middle", 147 | "containerId": "zoIZ5IbBrJtOIpJcanTSk", 148 | "originalText": "MySQL" 149 | }, 150 | { 151 | "type": "arrow", 152 | "version": 809, 153 | "versionNonce": 399606834, 154 | "isDeleted": false, 155 | "id": "QwHxD7L0DzHR8TS7cA3aL", 156 | "fillStyle": "solid", 157 | "strokeWidth": 1, 158 | "strokeStyle": "solid", 159 | "roughness": 1, 160 | "opacity": 100, 161 | "angle": 0, 162 | "x": 1592.3065338505764, 163 | "y": 527.9994907912549, 164 | "strokeColor": "#000000", 165 | "backgroundColor": "#ffffff", 166 | "width": 51.16166912762901, 167 | "height": 1.0973207198333768, 168 | "seed": 1398792558, 169 | "groupIds": [], 170 | "roundness": { 171 | "type": 2 172 | }, 173 | "boundElements": null, 174 | "updated": 1679070080414, 175 | "link": null, 176 | "locked": false, 177 | "startBinding": { 178 | "focus": 0.00236031876921256, 179 | "gap": 7.4193866926937835, 180 | "elementId": "KXKAfr36NEVJaKqVeM5IU" 181 | }, 182 | "endBinding": { 183 | "focus": 0.12131955462677589, 184 | "gap": 6.748723983764649, 185 | "elementId": "zoIZ5IbBrJtOIpJcanTSk" 186 | }, 187 | "lastCommittedPoint": null, 188 | "startArrowhead": null, 189 | "endArrowhead": null, 190 | "points": [ 191 | [ 192 | 0, 193 | 0 194 | ], 195 | [ 196 | 51.16166912762901, 197 | -1.0973207198333768 198 | ] 199 | ] 200 | }, 201 | { 202 | "type": "rectangle", 203 | "version": 1591, 204 | "versionNonce": 523299950, 205 | "isDeleted": false, 206 | "id": "06KHH2hYG1vQKRyUtgNL4", 207 | "fillStyle": "hachure", 208 | "strokeWidth": 1, 209 | "strokeStyle": "solid", 210 | "roughness": 1, 211 | "opacity": 100, 212 | "angle": 0, 213 | "x": 800.5995098056081, 214 | "y": 476.50828361511236, 215 | "strokeColor": "#000000", 216 | "backgroundColor": "#be4bdb", 217 | "width": 209.18356323242188, 218 | "height": 99.67071533203125, 219 | "seed": 2082190450, 220 | "groupIds": [], 221 | "roundness": { 222 | "type": 3 223 | }, 224 | "boundElements": [ 225 | { 226 | "id": "QwHxD7L0DzHR8TS7cA3aL", 227 | "type": "arrow" 228 | }, 229 | { 230 | "id": "lrAj6v6Sp0xzBLpzJW0H_", 231 | "type": "arrow" 232 | }, 233 | { 234 | "type": "text", 235 | "id": "JHYlgN9K1op69bQ_gTjVz" 236 | } 237 | ], 238 | "updated": 1679070080414, 239 | "link": null, 240 | "locked": false 241 | }, 242 | { 243 | "type": "text", 244 | "version": 529, 245 | "versionNonce": 2057217266, 246 | "isDeleted": false, 247 | "id": "JHYlgN9K1op69bQ_gTjVz", 248 | "fillStyle": "hachure", 249 | "strokeWidth": 1, 250 | "strokeStyle": "solid", 251 | "roughness": 1, 252 | "opacity": 100, 253 | "angle": 0, 254 | "x": 862.6453090609792, 255 | "y": 509.543641281128, 256 | "strokeColor": "#000000", 257 | "backgroundColor": "transparent", 258 | "width": 85.09196472167969, 259 | "height": 33.6, 260 | "seed": 738034606, 261 | "groupIds": [], 262 | "roundness": null, 263 | "boundElements": null, 264 | "updated": 1679070080418, 265 | "link": null, 266 | "locked": false, 267 | "fontSize": 28, 268 | "fontFamily": 1, 269 | "text": "Consul", 270 | "textAlign": "center", 271 | "verticalAlign": "middle", 272 | "containerId": "06KHH2hYG1vQKRyUtgNL4", 273 | "originalText": "Consul" 274 | }, 275 | { 276 | "type": "rectangle", 277 | "version": 1685, 278 | "versionNonce": 1509218674, 279 | "isDeleted": false, 280 | "id": "ZLSYc8gNS7Uv1GkF1lnqd", 281 | "fillStyle": "hachure", 282 | "strokeWidth": 1, 283 | "strokeStyle": "solid", 284 | "roughness": 1, 285 | "opacity": 100, 286 | "angle": 0, 287 | "x": 1089.0764690341236, 288 | "y": 476.50828361511236, 289 | "strokeColor": "#000000", 290 | "backgroundColor": "#fd7e14", 291 | "width": 209.18356323242188, 292 | "height": 99.67071533203125, 293 | "seed": 1062229554, 294 | "groupIds": [], 295 | "roundness": { 296 | "type": 3 297 | }, 298 | "boundElements": [ 299 | { 300 | "id": "QwHxD7L0DzHR8TS7cA3aL", 301 | "type": "arrow" 302 | }, 303 | { 304 | "id": "lrAj6v6Sp0xzBLpzJW0H_", 305 | "type": "arrow" 306 | }, 307 | { 308 | "id": "4l95zQSRvT8QpQQN3H11Q", 309 | "type": "arrow" 310 | }, 311 | { 312 | "type": "text", 313 | "id": "HGqhmVwj5LGrUUG3xQOJZ" 314 | } 315 | ], 316 | "updated": 1679070080414, 317 | "link": null, 318 | "locked": false 319 | }, 320 | { 321 | "type": "text", 322 | "version": 634, 323 | "versionNonce": 2143279918, 324 | "isDeleted": false, 325 | "id": "HGqhmVwj5LGrUUG3xQOJZ", 326 | "fillStyle": "hachure", 327 | "strokeWidth": 1, 328 | "strokeStyle": "solid", 329 | "roughness": 1, 330 | "opacity": 100, 331 | "angle": 0, 332 | "x": 1157.5482631625416, 333 | "y": 509.543641281128, 334 | "strokeColor": "#000000", 335 | "backgroundColor": "transparent", 336 | "width": 72.23997497558594, 337 | "height": 33.6, 338 | "seed": 1563221486, 339 | "groupIds": [], 340 | "roundness": null, 341 | "boundElements": null, 342 | "updated": 1679070080419, 343 | "link": null, 344 | "locked": false, 345 | "fontSize": 28, 346 | "fontFamily": 1, 347 | "text": "Vault", 348 | "textAlign": "center", 349 | "verticalAlign": "middle", 350 | "containerId": "ZLSYc8gNS7Uv1GkF1lnqd", 351 | "originalText": "Vault" 352 | }, 353 | { 354 | "type": "arrow", 355 | "version": 118, 356 | "versionNonce": 723357678, 357 | "isDeleted": false, 358 | "id": "lrAj6v6Sp0xzBLpzJW0H_", 359 | "fillStyle": "hachure", 360 | "strokeWidth": 1, 361 | "strokeStyle": "solid", 362 | "roughness": 1, 363 | "opacity": 100, 364 | "angle": 0, 365 | "x": 1017.6773448886158, 366 | "y": 526.792527501444, 367 | "strokeColor": "#000000", 368 | "backgroundColor": "transparent", 369 | "width": 70.0482177734375, 370 | "height": 1.533813296576568, 371 | "seed": 2131410930, 372 | "groupIds": [], 373 | "roundness": { 374 | "type": 2 375 | }, 376 | "boundElements": null, 377 | "updated": 1679070080414, 378 | "link": null, 379 | "locked": false, 380 | "startBinding": { 381 | "focus": -0.0017260075188349057, 382 | "gap": 7.8942718505859375, 383 | "elementId": "06KHH2hYG1vQKRyUtgNL4" 384 | }, 385 | "endBinding": { 386 | "focus": 0.11944880490231617, 387 | "gap": 1.3509063720703125, 388 | "elementId": "ZLSYc8gNS7Uv1GkF1lnqd" 389 | }, 390 | "lastCommittedPoint": null, 391 | "startArrowhead": null, 392 | "endArrowhead": null, 393 | "points": [ 394 | [ 395 | 0, 396 | 0 397 | ], 398 | [ 399 | 70.0482177734375, 400 | -1.533813296576568 401 | ] 402 | ] 403 | }, 404 | { 405 | "type": "arrow", 406 | "version": 203, 407 | "versionNonce": 1261841906, 408 | "isDeleted": false, 409 | "id": "4l95zQSRvT8QpQQN3H11Q", 410 | "fillStyle": "hachure", 411 | "strokeWidth": 1, 412 | "strokeStyle": "solid", 413 | "roughness": 1, 414 | "opacity": 100, 415 | "angle": 0, 416 | "x": 1303.821240991521, 417 | "y": 525.8250392485547, 418 | "strokeColor": "#000000", 419 | "backgroundColor": "transparent", 420 | "width": 65.9580799093128, 421 | "height": 1.068881752072329, 422 | "seed": 1542909998, 423 | "groupIds": [], 424 | "roundness": { 425 | "type": 2 426 | }, 427 | "boundElements": null, 428 | "updated": 1679070080414, 429 | "link": null, 430 | "locked": false, 431 | "startBinding": { 432 | "focus": -0.13384970593313603, 433 | "gap": 5.561208724975586, 434 | "elementId": "ZLSYc8gNS7Uv1GkF1lnqd" 435 | }, 436 | "endBinding": { 437 | "focus": 0.04358026736812166, 438 | "gap": 6.107826257048487, 439 | "elementId": "KXKAfr36NEVJaKqVeM5IU" 440 | }, 441 | "lastCommittedPoint": null, 442 | "startArrowhead": null, 443 | "endArrowhead": null, 444 | "points": [ 445 | [ 446 | 0, 447 | 0 448 | ], 449 | [ 450 | 65.9580799093128, 451 | 1.068881752072329 452 | ] 453 | ] 454 | }, 455 | { 456 | "type": "rectangle", 457 | "version": 267, 458 | "versionNonce": 259337902, 459 | "isDeleted": false, 460 | "id": "tgfUJ6Ptl2YrTwNBvZIa-", 461 | "fillStyle": "solid", 462 | "strokeWidth": 1, 463 | "strokeStyle": "solid", 464 | "roughness": 1, 465 | "opacity": 100, 466 | "angle": 0, 467 | "x": 1414.6810069979908, 468 | "y": 560.6563587188721, 469 | "strokeColor": "#000000", 470 | "backgroundColor": "#ffffff", 471 | "width": 132, 472 | "height": 41, 473 | "seed": 928117170, 474 | "groupIds": [], 475 | "roundness": { 476 | "type": 3 477 | }, 478 | "boundElements": [ 479 | { 480 | "type": "text", 481 | "id": "ALu-29Qutu2EgJlmLEe7Z" 482 | } 483 | ], 484 | "updated": 1679070080414, 485 | "link": null, 486 | "locked": false 487 | }, 488 | { 489 | "type": "text", 490 | "version": 104, 491 | "versionNonce": 110917038, 492 | "isDeleted": false, 493 | "id": "ALu-29Qutu2EgJlmLEe7Z", 494 | "fillStyle": "solid", 495 | "strokeWidth": 1, 496 | "strokeStyle": "solid", 497 | "roughness": 1, 498 | "opacity": 100, 499 | "angle": 0, 500 | "x": 1424.181068033147, 501 | "y": 569.1563587188721, 502 | "strokeColor": "#000000", 503 | "backgroundColor": "#ffffff", 504 | "width": 112.9998779296875, 505 | "height": 24, 506 | "seed": 340048494, 507 | "groupIds": [], 508 | "roundness": null, 509 | "boundElements": null, 510 | "updated": 1679070080413, 511 | "link": null, 512 | "locked": false, 513 | "fontSize": 20, 514 | "fontFamily": 1, 515 | "text": "spring-vault", 516 | "textAlign": "center", 517 | "verticalAlign": "middle", 518 | "containerId": "tgfUJ6Ptl2YrTwNBvZIa-", 519 | "originalText": "spring-vault" 520 | } 521 | ], 522 | "appState": { 523 | "gridSize": null, 524 | "viewBackgroundColor": "#ffffff" 525 | }, 526 | "files": {} 527 | } -------------------------------------------------------------------------------- /documentation/spring-vault-approle-mysql.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivangfr/springboot-vault-examples/265973cdfe525bfb78630ca5eebafbf655bbfbad/documentation/spring-vault-approle-mysql.jpeg -------------------------------------------------------------------------------- /init-environment.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source scripts/my-functions.sh 4 | 5 | MYSQL_VERSION="9.1.0" 6 | VAULT_VERSION="1.18.2" 7 | CONSUL_VERSION="1.20.1" 8 | 9 | echo 10 | echo "Starting environment" 11 | echo "====================" 12 | 13 | echo 14 | echo "Creating network" 15 | echo "----------------" 16 | docker network create springboot-vault-examples 17 | 18 | echo 19 | echo "Starting consul container" 20 | echo "-------------------------" 21 | 22 | docker run -d --rm --name consul \ 23 | -p 8400:8400 \ 24 | -p 8500:8500 \ 25 | -p 8600:53/udp \ 26 | --network=springboot-vault-examples \ 27 | --health-cmd="curl -f http://localhost:8500/v1/status/leader || exit 1" \ 28 | hashicorp/consul:${CONSUL_VERSION} 29 | 30 | echo 31 | echo "Starting vault container" 32 | echo "------------------------" 33 | 34 | docker run -d --rm --name vault \ 35 | -p 8200:8200 \ 36 | -e "MYSQL_ROOT_PASSWORD=secret" \ 37 | -e "MYSQL_DATABASE=exampledb" \ 38 | --cap-add=IPC_LOCK \ 39 | -v ${PWD}/docker/vault:/my/vault \ 40 | --network=springboot-vault-examples \ 41 | hashicorp/vault:${VAULT_VERSION} vault server -config=/my/vault/config/config.hcl 42 | 43 | echo 44 | wait_for_container_log "consul" "Node info in sync" 45 | 46 | echo 47 | wait_for_container_log "vault" "Vault server started!" 48 | 49 | echo 50 | echo "Starting mysql container" 51 | echo "------------------------" 52 | 53 | docker run -d --rm --name mysql \ 54 | -p 3306:3306 \ 55 | -e "MYSQL_ROOT_PASSWORD=secret" \ 56 | -e "MYSQL_DATABASE=exampledb" \ 57 | --network=springboot-vault-examples \ 58 | --health-cmd="mysqladmin ping -u root -p$${MYSQL_ROOT_PASSWORD}" \ 59 | mysql:${MYSQL_VERSION} 60 | 61 | echo 62 | echo "Starting mysql-2 container" 63 | echo "--------------------------" 64 | 65 | docker run -d --rm --name mysql-2 \ 66 | -p 3307:3306 \ 67 | -e "MYSQL_ROOT_PASSWORD=secret" \ 68 | -e "MYSQL_DATABASE=exampledb" \ 69 | --network=springboot-vault-examples \ 70 | --health-cmd="mysqladmin ping -u root -p$${MYSQL_ROOT_PASSWORD}" \ 71 | mysql:${MYSQL_VERSION} 72 | 73 | echo 74 | echo "Building cassandra image" 75 | echo "------------------------" 76 | 77 | docker build -t springboot-vault-examples_cassandra:latest docker/cassandra 78 | 79 | echo 80 | echo "Starting cassandra container" 81 | echo "----------------------------" 82 | 83 | docker run -d --rm --name cassandra \ 84 | -p 9042:9042 \ 85 | -p 7199:7199 \ 86 | -p 9160:9160 \ 87 | --network=springboot-vault-examples \ 88 | --health-cmd="cqlsh -ucassandra -pcassandra < /dev/null" \ 89 | springboot-vault-examples_cassandra:latest 90 | 91 | echo 92 | wait_for_container_log "mysql" "port: 3306" 93 | 94 | echo 95 | wait_for_container_log "mysql-2" "port: 3306" 96 | 97 | echo 98 | wait_for_container_log "cassandra" "Created default superuser role" 99 | 100 | source scripts/unseal-vault-enable-approle-databases.sh 101 | 102 | source scripts/setup-spring-cloud-vault-approle-mysql.sh 103 | source scripts/setup-spring-cloud-vault-approle-cassandra.sh 104 | source scripts/setup-spring-vault-approle-mysql.sh 105 | source scripts/setup-spring-vault-approle-multi-datasources-mysql.sh 106 | 107 | echo 108 | echo "*********************************************" 109 | echo "VAULT_ROOT_TOKEN=${VAULT_ROOT_TOKEN}" 110 | echo "*********************************************" 111 | 112 | echo 113 | echo "Environment Up and Running" 114 | echo "==========================" 115 | echo -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.4.0 9 | 10 | 11 | com.ivanfranchin 12 | springboot-vault-examples 13 | 1.0.0 14 | pom 15 | springboot-vault-examples 16 | Demo project for Spring Boot 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 21 32 | 2.7.0 33 | 3.4.4 34 | 35 | 36 | 37 | org.springdoc 38 | springdoc-openapi-starter-webmvc-ui 39 | ${springdoc-openapi.version} 40 | 41 | 42 | 43 | 44 | 45 | 46 | com.google.cloud.tools 47 | jib-maven-plugin 48 | ${jib-maven-plugin.version} 49 | 50 | 51 | 52 | 53 | 54 | spring-cloud-vault-approle-mysql/student-service 55 | spring-cloud-vault-approle-cassandra/book-service 56 | spring-vault-approle-mysql/movie-service 57 | spring-vault-approle-multi-datasources-mysql/restaurant-service 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /remove-docker-images.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source scripts/my-functions.sh 4 | 5 | check_script_input_parameter $1 6 | 7 | if [ "$1" = "spring-vault-approle-mysql" ] || 8 | [ "$1" = "all" ]; 9 | then 10 | 11 | docker rmi ivanfranchin/movie-service:1.0.0 12 | 13 | fi 14 | 15 | if [ "$1" = "spring-vault-approle-multi-datasources-mysql" ] || 16 | [ "$1" = "all" ]; 17 | then 18 | 19 | docker rmi ivanfranchin/restaurant-service:1.0.0 20 | 21 | fi 22 | 23 | if [ "$1" = "spring-cloud-vault-approle-mysql" ] || 24 | [ "$1" = "all" ]; 25 | then 26 | 27 | docker rmi ivanfranchin/student-service:1.0.0 28 | 29 | fi 30 | 31 | if [ "$1" = "spring-cloud-vault-approle-cassandra" ] || 32 | [ "$1" = "all" ]; 33 | then 34 | 35 | docker rmi ivanfranchin/book-service:1.0.0 36 | 37 | fi 38 | 39 | if [ "$1" = "all" ]; 40 | then 41 | 42 | docker rmi springboot-vault-examples_cassandra 43 | 44 | fi 45 | -------------------------------------------------------------------------------- /scripts/my-functions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | TIMEOUT=120 4 | 5 | # -- wait_for_container_log -- 6 | # $1: docker container name 7 | # S2: spring value to wait to appear in container logs 8 | function wait_for_container_log() { 9 | local log_waiting="Waiting for string '$2' in the $1 logs ..." 10 | echo "${log_waiting} It will timeout in ${TIMEOUT}s" 11 | SECONDS=0 12 | 13 | while true ; do 14 | local log=$(docker logs $1 2>&1 | grep "$2") 15 | if [ -n "$log" ] ; then 16 | echo $log 17 | break 18 | fi 19 | 20 | if [ $SECONDS -ge $TIMEOUT ] ; then 21 | echo "${log_waiting} TIMEOUT" 22 | break; 23 | fi 24 | sleep 1 25 | done 26 | } 27 | 28 | # -- check_script_input_parameter -- 29 | # $1: input parameter 30 | function check_script_input_parameter() { 31 | if [ "$1" != "all" ] && 32 | [ "$1" != "spring-vault-approle-mysql" ] && 33 | [ "$1" != "spring-vault-approle-multi-datasources-mysql" ] && 34 | [ "$1" != "spring-cloud-vault-approle-mysql" ] && 35 | [ "$1" != "spring-cloud-vault-approle-cassandra" ]; 36 | then 37 | printf "Invalid example name provided!" 38 | printf "\nValid Parameters:" 39 | 40 | printf "\n\tall" 41 | printf "\n\tspring-vault-approle-mysql" 42 | 43 | printf "\n\tspring-vault-approle-multi-datasources-mysql" 44 | printf "\n\tspring-cloud-vault-approle-mysql" 45 | printf "\n\tspring-cloud-vault-approle-cassandra" 46 | 47 | printf "\n" 48 | exit 1 49 | fi 50 | } -------------------------------------------------------------------------------- /scripts/setup-spring-cloud-vault-approle-cassandra.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -z "$VAULT_ROOT_TOKEN" ]; then 4 | echo "WARNING: set to VAULT_ROOT_TOKEN environment variable, the root token generated while running the script unseal-vault-enable-approle-databases.sh!" 5 | exit 1 6 | fi 7 | 8 | VAULT_ADDR=http://localhost:8200 9 | 10 | CUSTOM_ROLE_ID="book-service-role-id" 11 | DATABASE_USER="book-user" 12 | 13 | DATABASE_ROLE="book-role" 14 | DATABASE_ROLE_POLICY="book-policy" 15 | 16 | KV_ROLE_POLICY="kv-policy" 17 | 18 | echo 19 | echo "=======================" 20 | echo "-- Database (Cassandra)" 21 | 22 | echo 23 | echo "--> creating Database role '${DATABASE_ROLE}' ..." 24 | curl -X POST -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" -d '{"db_name": "cassandra", "creation_statements":"CREATE USER '"'"'{{username}}'"'"' WITH PASSWORD '"'"'{{password}}'"'"' NOSUPERUSER; GRANT ALL PERMISSIONS ON ALL KEYSPACES TO {{username}}"}, "default_ttl": "2m", "max_ttl": "10m"' ${VAULT_ADDR}/v1/database/roles/${DATABASE_ROLE} 25 | #-- Note. Setting the 'default_ttl' and 'max_ttl' in the command above does not work! In order to test shorter times, change 'config.hcl' file. 26 | 27 | echo "--> setting Database policy '${DATABASE_ROLE_POLICY}' ..." 28 | curl -X POST -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" -d '{"policy":"path \"database/creds/'${DATABASE_ROLE}'\" {policy=\"read\"} path \"sys/renew/database/creds/*\" {capabilities=[\"update\"]}"}' ${VAULT_ADDR}/v1/sys/policy/${DATABASE_ROLE_POLICY} 29 | 30 | echo "--> testing Database role '${DATABASE_ROLE}' with ROOT_TOKEN ..." 31 | curl -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" ${VAULT_ADDR}/v1/database/creds/${DATABASE_ROLE} 32 | 33 | echo 34 | echo "--> List of leases" 35 | curl -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -X LIST ${VAULT_ADDR}/v1/sys/leases/lookup/database/creds/${DATABASE_ROLE} 36 | 37 | echo 38 | echo "===================" 39 | echo "-- Static KV secret" 40 | 41 | echo "setting message KV secret ..." 42 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d '{"message": "Hello from book-service"}' ${VAULT_ADDR}/v1/secret/book-service 43 | 44 | echo "--> setting KV secret policy '${KV_ROLE_POLICY}' ..." 45 | curl -X POST -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" -d '{"policy":"path \"secret/*\" {policy=\"read\"}"}' ${VAULT_ADDR}/v1/sys/policy/${KV_ROLE_POLICY} 46 | 47 | echo "====================================" 48 | echo "-- AppRole (login without secret-id)" 49 | 50 | echo 51 | echo "--> creating AppRole '${DATABASE_USER}' with policies '${DATABASE_ROLE_POLICY}' and '${KV_ROLE_POLICY}' ..." 52 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d '{"policies": ["'${DATABASE_ROLE_POLICY}'", "'${KV_ROLE_POLICY}'"], "bound_cidr_list": "0.0.0.0/0", "bind_secret_id": false}' ${VAULT_ADDR}/v1/auth/approle/role/${DATABASE_USER} 53 | 54 | echo "--> update ROLE_ID with custom value '${CUSTOM_ROLE_ID}'" 55 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d '{"role_id": "'${CUSTOM_ROLE_ID}'"}' ${VAULT_ADDR}/v1/auth/approle/role/${DATABASE_USER}/role-id 56 | 57 | echo "--> fetching the identifier of the AppRole '${DATABASE_USER}' ..." 58 | ROLE_ID=$(curl -s -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" ${VAULT_ADDR}/v1/auth/approle/role/${DATABASE_USER}/role-id | jq -r .data.role_id) 59 | echo "ROLE_ID=${ROLE_ID}" 60 | 61 | echo 62 | echo "--> getting client token ..." 63 | CLIENT_TOKEN=$(curl -X POST -s -d '{"role_id":"'${ROLE_ID}'"}' ${VAULT_ADDR}/v1/auth/approle/login | jq -r .auth.client_token) 64 | echo "CLIENT_TOKEN=${CLIENT_TOKEN}" 65 | 66 | echo 67 | echo "--> testing Cassandra role '${DATABASE_ROLE}' with CLIENT_TOKEN ..." 68 | curl -i -H "X-Vault-Token:${CLIENT_TOKEN}" ${VAULT_ADDR}/v1/database/creds/${DATABASE_ROLE} 69 | echo 70 | 71 | echo "--> testing message KV secret ..." 72 | curl -i -H "X-Vault-Token:${CLIENT_TOKEN}" ${VAULT_ADDR}/v1/secret/book-service 73 | -------------------------------------------------------------------------------- /scripts/setup-spring-cloud-vault-approle-mysql.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -z "$VAULT_ROOT_TOKEN" ]; then 4 | echo "WARNING: set to VAULT_ROOT_TOKEN environment variable, the root token generated while running the script unseal-vault-enable-approle-databases.sh!" 5 | exit 1 6 | fi 7 | 8 | VAULT_ADDR=http://localhost:8200 9 | 10 | CUSTOM_ROLE_ID="student-service-role-id" 11 | DATABASE_USER="student-user" 12 | 13 | DATABASE_ROLE="student-role" 14 | DATABASE_ROLE_POLICY="student-policy" 15 | 16 | KV_ROLE_POLICY="kv-policy" 17 | 18 | echo 19 | echo "===================" 20 | echo "-- Database (MySQL)" 21 | 22 | echo 23 | echo "--> creating Database role '${DATABASE_ROLE}' ..." 24 | curl -X POST -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" -d '{"db_name": "mysql", "creation_statements":"CREATE USER '"'"'{{name}}'"'"'@'"'"'%'"'"' IDENTIFIED BY '"'"'{{password}}'"'"'; GRANT ALL ON *.* TO '"'"'{{name}}'"'"'@'"'"'%'"'"';"}, "default_ttl": "2m", "max_ttl": "10m"' ${VAULT_ADDR}/v1/database/roles/${DATABASE_ROLE} 25 | #-- Note. Setting the 'default_ttl' and 'max_ttl' in the command above does not work! In order to test shorter times, change 'config.hcl' file. 26 | 27 | echo "--> setting Database policy '${DATABASE_ROLE_POLICY}' ..." 28 | curl -X POST -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" -d '{"policy":"path \"database/creds/'${DATABASE_ROLE}'\" {policy=\"read\"} path \"sys/renew/database/creds/*\" {capabilities=[\"update\"]}"}' ${VAULT_ADDR}/v1/sys/policy/${DATABASE_ROLE_POLICY} 29 | 30 | echo "--> testing Database role '${DATABASE_ROLE}' with ROOT_TOKEN ..." 31 | curl -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" ${VAULT_ADDR}/v1/database/creds/${DATABASE_ROLE} 32 | 33 | echo 34 | echo "--> List of leases" 35 | curl -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -X LIST ${VAULT_ADDR}/v1/sys/leases/lookup/database/creds/${DATABASE_ROLE} 36 | 37 | echo 38 | echo "===================" 39 | echo "-- Static KV secret" 40 | 41 | echo "setting message KV secret ..." 42 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d '{"message": "Hello from student-service"}' ${VAULT_ADDR}/v1/secret/student-service 43 | 44 | echo "--> setting KV secret policy '${KV_ROLE_POLICY}' ..." 45 | curl -X POST -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" -d '{"policy":"path \"secret/*\" {policy=\"read\"}"}' ${VAULT_ADDR}/v1/sys/policy/${KV_ROLE_POLICY} 46 | 47 | echo "====================================" 48 | echo "-- AppRole (login without secret-id)" 49 | 50 | echo 51 | echo "--> creating AppRole '${DATABASE_USER}' with policies '${DATABASE_ROLE_POLICY}' and '${KV_ROLE_POLICY}' ..." 52 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d '{"policies": ["'${DATABASE_ROLE_POLICY}'", "'${KV_ROLE_POLICY}'"], "bound_cidr_list": "0.0.0.0/0", "bind_secret_id": false}' ${VAULT_ADDR}/v1/auth/approle/role/${DATABASE_USER} 53 | 54 | echo "--> update ROLE_ID with custom value '${CUSTOM_ROLE_ID}'" 55 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d '{"role_id": "'${CUSTOM_ROLE_ID}'"}' ${VAULT_ADDR}/v1/auth/approle/role/${DATABASE_USER}/role-id 56 | 57 | echo "--> fetching the identifier of the AppRole '${DATABASE_USER}' ..." 58 | ROLE_ID=$(curl -s -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" ${VAULT_ADDR}/v1/auth/approle/role/${DATABASE_USER}/role-id | jq -r .data.role_id) 59 | echo "ROLE_ID=${ROLE_ID}" 60 | 61 | echo 62 | echo "--> getting client token ..." 63 | CLIENT_TOKEN=$(curl -X POST -s -d '{"role_id":"'${ROLE_ID}'"}' ${VAULT_ADDR}/v1/auth/approle/login | jq -r .auth.client_token) 64 | echo "CLIENT_TOKEN=${CLIENT_TOKEN}" 65 | 66 | echo 67 | echo "--> testing MySQL role '${DATABASE_ROLE}' with CLIENT_TOKEN ..." 68 | curl -i -H "X-Vault-Token:${CLIENT_TOKEN}" ${VAULT_ADDR}/v1/database/creds/${DATABASE_ROLE} 69 | echo 70 | 71 | echo "--> testing message KV secret ..." 72 | curl -i -H "X-Vault-Token:${CLIENT_TOKEN}" ${VAULT_ADDR}/v1/secret/student-service 73 | -------------------------------------------------------------------------------- /scripts/setup-spring-vault-approle-multi-datasources-mysql.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -z "$VAULT_ROOT_TOKEN" ]; then 4 | echo "WARNING: set to VAULT_ROOT_TOKEN environment variable, the root token generated while running the script unseal-vault-enable-approle-databases.sh!" 5 | exit 1 6 | fi 7 | 8 | VAULT_ADDR=http://localhost:8200 9 | 10 | CUSTOM_ROLE_ID="restaurant-service-role-id" 11 | DATABASE_USER="restaurant-user" 12 | 13 | CUSTOMER_DATABASE_ROLE="customer-role" 14 | CUSTOMER_DATABASE_ROLE_POLICY="customer-policy" 15 | 16 | DISH_DATABASE_ROLE="dish-role" 17 | DISH_DATABASE_ROLE_POLICY="dish-policy" 18 | 19 | KV_ROLE_POLICY="kv-policy" 20 | 21 | echo 22 | echo "===================" 23 | echo "-- Database (MySQL)" 24 | 25 | echo 26 | echo "--> creating Database role '${CUSTOMER_DATABASE_ROLE}' ..." 27 | curl -X POST -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" -d '{"db_name": "mysql", "creation_statements":"CREATE USER '"'"'{{name}}'"'"'@'"'"'%'"'"' IDENTIFIED BY '"'"'{{password}}'"'"'; GRANT ALL ON *.* TO '"'"'{{name}}'"'"'@'"'"'%'"'"';"}, "default_ttl": "2m", "max_ttl": "10m"' ${VAULT_ADDR}/v1/database/roles/${CUSTOMER_DATABASE_ROLE} 28 | #-- Note. Setting the 'default_ttl' and 'max_ttl' in the command above does not work! In order to test shorter times, change 'config.hcl' file. 29 | 30 | echo "--> setting Database policy '${CUSTOMER_DATABASE_ROLE_POLICY}' ..." 31 | curl -X POST -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" -d '{"policy":"path \"database/creds/'${CUSTOMER_DATABASE_ROLE}'\" {policy=\"read\"} path \"sys/renew/database/creds/*\" {capabilities=[\"update\"]}"}' ${VAULT_ADDR}/v1/sys/policy/${CUSTOMER_DATABASE_ROLE_POLICY} 32 | 33 | echo "--> testing Database role '${CUSTOMER_DATABASE_ROLE}' with ROOT_TOKEN ..." 34 | curl -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" ${VAULT_ADDR}/v1/database/creds/${CUSTOMER_DATABASE_ROLE} 35 | 36 | echo 37 | echo "--> List of leases" 38 | curl -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -X LIST ${VAULT_ADDR}/v1/sys/leases/lookup/database/creds/${CUSTOMER_DATABASE_ROLE} 39 | 40 | echo 41 | echo "=====================" 42 | echo "-- Database (MySQL-2)" 43 | 44 | echo 45 | echo "--> creating Database role '${DISH_DATABASE_ROLE}' ..." 46 | curl -X POST -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" -d '{"db_name": "mysql-2", "creation_statements":"CREATE USER '"'"'{{name}}'"'"'@'"'"'%'"'"' IDENTIFIED BY '"'"'{{password}}'"'"'; GRANT ALL ON *.* TO '"'"'{{name}}'"'"'@'"'"'%'"'"';"}, "default_ttl": "2m", "max_ttl": "10m"' ${VAULT_ADDR}/v1/database/roles/${DISH_DATABASE_ROLE} 47 | #-- Note. Setting the 'default_ttl' and 'max_ttl' in the command above does not work! In order to test shorter times, change 'config.hcl' file. 48 | 49 | echo "--> setting Database policy '${DISH_DATABASE_ROLE_POLICY}' ..." 50 | curl -X POST -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" -d '{"policy":"path \"database/creds/'${DISH_DATABASE_ROLE}'\" {policy=\"read\"} path \"sys/renew/database/creds/*\" {capabilities=[\"update\"]}"}' ${VAULT_ADDR}/v1/sys/policy/${DISH_DATABASE_ROLE_POLICY} 51 | 52 | echo "--> testing Database role '${DISH_DATABASE_ROLE}' with ROOT_TOKEN ..." 53 | curl -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" ${VAULT_ADDR}/v1/database/creds/${DISH_DATABASE_ROLE} 54 | 55 | echo 56 | echo "--> List of leases" 57 | curl -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -X LIST ${VAULT_ADDR}/v1/sys/leases/lookup/database/creds/${DISH_DATABASE_ROLE} 58 | 59 | echo 60 | echo "===================" 61 | echo "-- Static KV secret" 62 | 63 | echo "setting message KV secret ..." 64 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d '{"message": "Hello from restaurant-service"}' ${VAULT_ADDR}/v1/secret/restaurant-service 65 | 66 | echo "--> setting KV secret policy '${KV_ROLE_POLICY}' ..." 67 | curl -X POST -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" -d '{"policy":"path \"secret/*\" {policy=\"read\"}"}' ${VAULT_ADDR}/v1/sys/policy/${KV_ROLE_POLICY} 68 | 69 | echo "====================================" 70 | echo "-- AppRole (login without secret-id)" 71 | 72 | echo 73 | echo "--> creating AppRole '${DATABASE_USER}' with policies '${CUSTOMER_DATABASE_ROLE_POLICY}', '${DISH_DATABASE_ROLE_POLICY}', ${KV_ROLE_POLICY} ..." 74 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d '{"policies": ["'${CUSTOMER_DATABASE_ROLE_POLICY}'", "'${DISH_DATABASE_ROLE_POLICY}'", "'${KV_ROLE_POLICY}'"], "bound_cidr_list": "0.0.0.0/0", "bind_secret_id": false}' ${VAULT_ADDR}/v1/auth/approle/role/${DATABASE_USER} 75 | 76 | echo "--> update ROLE_ID with custom value '${CUSTOM_ROLE_ID}'" 77 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d '{"role_id": "'${CUSTOM_ROLE_ID}'"}' ${VAULT_ADDR}/v1/auth/approle/role/${DATABASE_USER}/role-id 78 | 79 | echo "--> fetching the identifier of the AppRole '${DATABASE_USER}' ..." 80 | ROLE_ID=$(curl -s -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" ${VAULT_ADDR}/v1/auth/approle/role/${DATABASE_USER}/role-id | jq -r .data.role_id) 81 | echo "ROLE_ID=${ROLE_ID}" 82 | 83 | echo 84 | echo "--> getting client token ..." 85 | CLIENT_TOKEN=$(curl -X POST -s -d '{"role_id":"'${ROLE_ID}'"}' ${VAULT_ADDR}/v1/auth/approle/login | jq -r .auth.client_token) 86 | echo "CLIENT_TOKEN=${CLIENT_TOKEN}" 87 | 88 | echo 89 | echo "--> testing MySQL role '${CUSTOMER_DATABASE_ROLE}' with CLIENT_TOKEN ..." 90 | curl -i -H "X-Vault-Token:${CLIENT_TOKEN}" ${VAULT_ADDR}/v1/database/creds/${CUSTOMER_DATABASE_ROLE} 91 | 92 | echo 93 | echo "--> testing MySQL-2 role '${DISH_DATABASE_ROLE}' with CLIENT_TOKEN ..." 94 | curl -i -H "X-Vault-Token:${CLIENT_TOKEN}" ${VAULT_ADDR}/v1/database/creds/${DISH_DATABASE_ROLE} 95 | 96 | echo "--> testing message KV secret ..." 97 | curl -i -H "X-Vault-Token:${CLIENT_TOKEN}" ${VAULT_ADDR}/v1/secret/restaurant-service 98 | -------------------------------------------------------------------------------- /scripts/setup-spring-vault-approle-mysql.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -z "$VAULT_ROOT_TOKEN" ]; then 4 | echo "WARNING: set to VAULT_ROOT_TOKEN environment variable, the root token generated while running the script unseal-vault-enable-approle-databases.sh!" 5 | exit 1 6 | fi 7 | 8 | VAULT_ADDR=http://localhost:8200 9 | 10 | CUSTOM_ROLE_ID="movie-service-role-id" 11 | DATABASE_USER="movie-user" 12 | 13 | DATABASE_ROLE="movie-role" 14 | DATABASE_ROLE_POLICY="movie-policy" 15 | 16 | KV_ROLE_POLICY="kv-policy" 17 | 18 | echo 19 | echo "===================" 20 | echo "-- Database (MySQL)" 21 | 22 | echo 23 | echo "--> creating Database role '${DATABASE_ROLE}' ..." 24 | curl -X POST -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" -d '{"db_name": "mysql", "creation_statements":"CREATE USER '"'"'{{name}}'"'"'@'"'"'%'"'"' IDENTIFIED BY '"'"'{{password}}'"'"'; GRANT ALL ON *.* TO '"'"'{{name}}'"'"'@'"'"'%'"'"';"}, "default_ttl": "2m", "max_ttl": "10m"' ${VAULT_ADDR}/v1/database/roles/${DATABASE_ROLE} 25 | #-- Note. Setting the 'default_ttl' and 'max_ttl' in the command above does not work! In order to test shorter times, change 'config.hcl' file. 26 | 27 | echo "--> setting Database policy '${DATABASE_ROLE_POLICY}' ..." 28 | curl -X POST -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" -d '{"policy":"path \"database/creds/'${DATABASE_ROLE}'\" {policy=\"read\"} path \"sys/renew/database/creds/*\" {capabilities=[\"update\"]}"}' ${VAULT_ADDR}/v1/sys/policy/${DATABASE_ROLE_POLICY} 29 | 30 | echo "--> testing Database role '${DATABASE_ROLE}' with ROOT_TOKEN ..." 31 | curl -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" ${VAULT_ADDR}/v1/database/creds/${DATABASE_ROLE} 32 | 33 | echo 34 | echo "--> List of leases" 35 | curl -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -X LIST ${VAULT_ADDR}/v1/sys/leases/lookup/database/creds/${DATABASE_ROLE} 36 | 37 | echo 38 | echo "===================" 39 | echo "-- Static KV secret" 40 | 41 | echo "setting message KV secret ..." 42 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d '{"message": "Hello from movie-service"}' ${VAULT_ADDR}/v1/secret/movie-service 43 | 44 | echo "--> setting KV secret policy '${KV_ROLE_POLICY}' ..." 45 | curl -X POST -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" -d '{"policy":"path \"secret/*\" {policy=\"read\"}"}' ${VAULT_ADDR}/v1/sys/policy/${KV_ROLE_POLICY} 46 | 47 | echo "====================================" 48 | echo "-- AppRole (login without secret-id)" 49 | 50 | echo 51 | echo "--> creating AppRole '${DATABASE_USER}' with policies '${DATABASE_ROLE_POLICY}' and '${KV_ROLE_POLICY}' ..." 52 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d '{"policies": ["'${DATABASE_ROLE_POLICY}'", "'${KV_ROLE_POLICY}'"], "bound_cidr_list": "0.0.0.0/0", "bind_secret_id": false}' ${VAULT_ADDR}/v1/auth/approle/role/${DATABASE_USER} 53 | 54 | echo "--> update ROLE_ID with custom value '${CUSTOM_ROLE_ID}'" 55 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d '{"role_id": "'${CUSTOM_ROLE_ID}'"}' ${VAULT_ADDR}/v1/auth/approle/role/${DATABASE_USER}/role-id 56 | 57 | echo "--> fetching the identifier of the AppRole '${DATABASE_USER}' ..." 58 | ROLE_ID=$(curl -s -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" ${VAULT_ADDR}/v1/auth/approle/role/${DATABASE_USER}/role-id | jq -r .data.role_id) 59 | echo "ROLE_ID=${ROLE_ID}" 60 | 61 | echo 62 | echo "--> getting client token ..." 63 | CLIENT_TOKEN=$(curl -X POST -s -d '{"role_id":"'${ROLE_ID}'"}' ${VAULT_ADDR}/v1/auth/approle/login | jq -r .auth.client_token) 64 | echo "CLIENT_TOKEN=${CLIENT_TOKEN}" 65 | 66 | echo 67 | echo "--> testing MySQL role '${DATABASE_ROLE}' with CLIENT_TOKEN ..." 68 | curl -i -H "X-Vault-Token:${CLIENT_TOKEN}" ${VAULT_ADDR}/v1/database/creds/${DATABASE_ROLE} 69 | echo 70 | 71 | echo "--> testing message KV secret ..." 72 | curl -i -H "X-Vault-Token:${CLIENT_TOKEN}" ${VAULT_ADDR}/v1/secret/movie-service 73 | -------------------------------------------------------------------------------- /scripts/unseal-vault-enable-approle-databases.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VAULT_ADDR=http://localhost:8200 4 | 5 | echo 6 | echo "=====================" 7 | echo "-- Initializing Vault" 8 | 9 | VAULT_KEYS=$(curl -X PUT -s -d '{ "secret_shares": 1, "secret_threshold": 1 }' ${VAULT_ADDR}/v1/sys/init | jq .) 10 | VAULT_KEY1=$(echo ${VAULT_KEYS} | jq -r .keys_base64[0]) 11 | VAULT_ROOT_TOKEN=$(echo ${VAULT_KEYS} | jq -r .root_token) 12 | 13 | echo 14 | echo "--> unsealing Vault ..." 15 | curl -X PUT -d '{ "key": "'${VAULT_KEY1}'" }' ${VAULT_ADDR}/v1/sys/unseal 16 | 17 | echo 18 | echo "--> Vault status" 19 | curl ${VAULT_ADDR}/v1/sys/init 20 | 21 | echo 22 | echo "====================================" 23 | echo "-- AppRole (login without secret-id)" 24 | 25 | echo 26 | echo "--> enabling the AppRole auth method ..." 27 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d '{"type": "approle"}' ${VAULT_ADDR}/v1/sys/auth/approle 28 | 29 | echo "================================" 30 | echo "-- KV Secrets Engine - Version 1" 31 | 32 | echo 33 | echo "--> enabling KV Secrets Engine ..." 34 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d '{"type": "kv", "description": "my KV Secrets Engine", "config": {"force_no_cache": true}}' ${VAULT_ADDR}/v1/sys/mounts/secret 35 | 36 | echo "========================" 37 | echo "-- Mounting Database ..." 38 | curl -X POST -i -H "X-Vault-Token:${VAULT_ROOT_TOKEN}" -d '{"type": "database"}' ${VAULT_ADDR}/v1/sys/mounts/database 39 | 40 | echo "--> configuring Cassandra plugin and connection ..." 41 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d "{\"plugin_name\": \"cassandra-database-plugin\", \"allowed_roles\": \"*\", \"hosts\": \"cassandra\", \"username\": \"cassandra\", \"password\": \"cassandra\", \"protocol_version\": 3}" ${VAULT_ADDR}/v1/database/config/cassandra 42 | 43 | echo "--> configuring MySQL plugin and connection ..." 44 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d "{\"plugin_name\": \"mysql-database-plugin\", \"allowed_roles\": \"*\", \"connection_url\": \"root:secret@tcp(mysql:3306)/\"}" ${VAULT_ADDR}/v1/database/config/mysql 45 | 46 | echo "--> configuring MySQL-2 plugin and connection ..." 47 | curl -X POST -i -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" -d "{\"plugin_name\": \"mysql-database-plugin\", \"allowed_roles\": \"*\", \"connection_url\": \"root:secret@tcp(mysql-2:3306)/\"}" ${VAULT_ADDR}/v1/database/config/mysql-2 48 | -------------------------------------------------------------------------------- /shutdown-environment.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo 4 | echo "Starting the environment shutdown" 5 | echo "=================================" 6 | 7 | echo 8 | echo "Removing containers" 9 | echo "-------------------" 10 | docker rm -fv mysql mysql-2 cassandra vault consul 11 | 12 | echo 13 | echo "Removing network" 14 | echo "----------------" 15 | docker network rm springboot-vault-examples 16 | 17 | echo 18 | echo "Environment shutdown successfully" 19 | echo "=================================" 20 | echo -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/README.md: -------------------------------------------------------------------------------- 1 | # springboot-vault-examples 2 | ## `> spring-cloud-vault-approle-cassandra` 3 | 4 | ## Project Diagram 5 | 6 | ![project-diagram](../documentation/spring-cloud-vault-approle-cassandra.jpeg) 7 | 8 | ## Application 9 | 10 | - ### book-service 11 | 12 | - [`Spring Boot`](https://docs.spring.io/spring-boot/index.html) application that manages `books` 13 | - It uses [`Cassandra`](https://cassandra.apache.org/) database as storage 14 | - It uses [`Spring Cloud Vault`](https://cloud.spring.io/spring-cloud-vault/reference/html/) 15 | - Credentials to access `Cassandra` is generated dynamically by [`Vault`](https://www.vaultproject.io) 16 | - **Leases are renewed but NOT rotated** 17 | - `AppRole` is the `Vault` authentication method used 18 | 19 | ## Prerequisite 20 | 21 | Before running this example, make sure the environment is initialized (see [Initialize Environment](https://github.com/ivangfr/springboot-vault-examples#initialize-environment) section in the main README). 22 | 23 | ## Start book-service 24 | 25 | ### Running with Maven Wrapper 26 | 27 | - In a terminal, make sure you are inside the `springboot-vault-examples` root folder; 28 | 29 | - Run the following command: 30 | ``` 31 | ./mvnw clean spring-boot:run \ 32 | --projects spring-cloud-vault-approle-cassandra/book-service \ 33 | -Dspring-boot.run.jvmArguments="-Dserver.port=9081" 34 | ``` 35 | 36 | ### Running as Docker Container 37 | 38 | - In a terminal, make sure you are inside the `springboot-vault-examples` root folder; 39 | 40 | - Build the Docker image: 41 | ``` 42 | ./build-docker-images.sh spring-cloud-vault-approle-cassandra 43 | ``` 44 | | Environment Variable | Description | 45 | |----------------------|--------------------------------------------------------------| 46 | | `VAULT_HOST` | Specify host of the `Vault` to use (default `localhost`) | 47 | | `VAULT_PORT` | Specify port of the `Vault` to use (default `8200`) | 48 | | `CONSUL_HOST` | Specify host of the `Consul` to use (default `localhost`) | 49 | | `CONSUL_PORT` | Specify port of the `Consul` to use (default `8500`) | 50 | | `CASSANDRA_HOST` | Specify host of the `Cassandra` to use (default `localhost`) | 51 | | `CASSANDRA_PORT` | Specify port of the `Cassandra` to use (default `9042`) | 52 | 53 | - Run the Docker container: 54 | ``` 55 | docker run --rm --name book-service -p 9081:8080 \ 56 | -e VAULT_HOST=vault -e CONSUL_HOST=consul -e CASSANDRA_HOST=cassandra \ 57 | --network springboot-vault-examples \ 58 | ivanfranchin/book-service:1.0.0 59 | ``` 60 | 61 | ## Using book-service 62 | 63 | You can access `book-service` Swagger website at http://localhost:9081/swagger-ui.html 64 | 65 | ## Useful Links & Commands 66 | 67 | - **Vault** 68 | 69 | > **Note**: In order to run some commands, you must have [`jq`](https://jqlang.github.io/jq/) installed in your machine. 70 | 71 | - Open a new terminal 72 | 73 | - Set to `VAULT_ROOT_TOKEN` environment variable the value obtained while [initializing the environment](https://github.com/ivangfr/springboot-vault-examples#initialize-environment) described in the main README. 74 | ``` 75 | VAULT_ROOT_TOKEN=... 76 | ``` 77 | 78 | - List of active leases for `database/creds/book-role` 79 | ``` 80 | curl -s -X LIST \ 81 | -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" \ 82 | http://localhost:8200/v1/sys/leases/lookup/database/creds/book-role | jq . 83 | ``` 84 | 85 | The response will be something like: 86 | ``` 87 | { 88 | "request_id": "c56d60d7-6792-7822-3e4d-b51ef8bea4ce", 89 | "lease_id": "", 90 | "renewable": false, 91 | "lease_duration": 0, 92 | "data": { 93 | "keys": [ 94 | "AsQLBqfNGNNNeeEFp2c6sQs9" 95 | ] 96 | }, 97 | "wrap_info": null, 98 | "warnings": null, 99 | "auth": null 100 | } 101 | ``` 102 | 103 | - See specific lease metadata 104 | ``` 105 | curl -s -X PUT \ 106 | -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" \ 107 | -d '{ "lease_id": "database/creds/book-role/AsQLBqfNGNNNeeEFp2c6sQs9" }' \ 108 | http://localhost:8200/v1/sys/leases/lookup | jq . 109 | ``` 110 | 111 | The response will be something like: 112 | ``` 113 | { 114 | "request_id": "4b7ee3f4-f891-f34f-5d5c-22ef2600fb55", 115 | "lease_id": "", 116 | "renewable": false, 117 | "lease_duration": 0, 118 | "data": { 119 | "expire_time": "2019-07-26T20:23:21.5935893Z", 120 | "id": "database/creds/book-role/AsQLBqfNGNNNeeEFp2c6sQs9", 121 | "issue_time": "2019-07-26T20:21:21.5935713Z", 122 | "last_renewal": null, 123 | "renewable": true, 124 | "ttl": 70 125 | }, 126 | "wrap_info": null, 127 | "warnings": null, 128 | "auth": null 129 | } 130 | ``` 131 | 132 | - **Cassandra** 133 | 134 | - Open a new terminal 135 | 136 | - Connect to `cqlsh` inside docker container and list books 137 | ``` 138 | docker exec -it cassandra cqlsh -ucassandra -pcassandra -k mycompany 139 | ``` 140 | > To exit `cqlsh`, type `exit` 141 | 142 | - List users 143 | ``` 144 | LIST USERS; 145 | ``` 146 | 147 | - **Consul** 148 | 149 | `Consul` can be accessed at http://localhost:8500 150 | 151 | ## Shutdown 152 | 153 | - Go to the terminal where the application is running and pressing `Ctrl+C`; 154 | - Stop the services started using the `init-environment` script as explained in [Shutdown Environment](https://github.com/ivangfr/springboot-vault-examples#shutdown-environment) section of the main README. 155 | 156 | ## Cleanup 157 | 158 | To remove the Docker image create by this example, go to a terminal and run the command below: 159 | ``` 160 | ./remove-docker-images.sh spring-cloud-vault-approle-cassandra 161 | ``` 162 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/book-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.ivanfranchin 7 | springboot-vault-examples 8 | 1.0.0 9 | ../../pom.xml 10 | 11 | book-service 12 | book-service 13 | Demo project for Spring Boot 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 2024.0.0 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-data-cassandra 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-web 38 | 39 | 40 | org.springframework.cloud 41 | spring-cloud-starter-bootstrap 42 | 43 | 44 | org.springframework.cloud 45 | spring-cloud-starter-consul-discovery 46 | 47 | 48 | org.springframework.cloud 49 | spring-cloud-vault-config-databases 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-test 55 | test 56 | 57 | 58 | 59 | 60 | 61 | org.springframework.cloud 62 | spring-cloud-dependencies 63 | ${spring-cloud.version} 64 | pom 65 | import 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.springframework.boot 74 | spring-boot-maven-plugin 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/book-service/src/main/java/com/ivanfranchin/bookservice/BookServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.bookservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | 7 | @EnableDiscoveryClient 8 | @SpringBootApplication 9 | public class BookServiceApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(BookServiceApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/book-service/src/main/java/com/ivanfranchin/bookservice/book/BookController.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.bookservice.book; 2 | 3 | import com.ivanfranchin.bookservice.book.model.Book; 4 | import com.ivanfranchin.bookservice.book.dto.BookResponse; 5 | import com.ivanfranchin.bookservice.book.dto.CreateBookRequest; 6 | import jakarta.validation.Valid; 7 | import org.springframework.core.env.Environment; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.ResponseStatus; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | import java.util.List; 17 | 18 | @RestController 19 | @RequestMapping("/api/books") 20 | public class BookController { 21 | 22 | private final BookRepository bookRepository; 23 | private final Environment environment; 24 | 25 | public BookController(BookRepository bookRepository, Environment environment) { 26 | this.bookRepository = bookRepository; 27 | this.environment = environment; 28 | } 29 | 30 | @GetMapping("/dbcredentials") 31 | public String getDBCredentials() { 32 | return String.format("%s/%s", 33 | environment.getProperty("spring.datasource.username"), 34 | environment.getProperty("spring.datasource.password")); 35 | } 36 | 37 | @GetMapping("/secretMessage") 38 | public String getSecretMessage() { 39 | return environment.getProperty("message"); 40 | } 41 | 42 | @GetMapping 43 | public List getBooks() { 44 | return bookRepository.findAll() 45 | .stream() 46 | .map(BookResponse::from) 47 | .toList(); 48 | } 49 | 50 | @ResponseStatus(HttpStatus.CREATED) 51 | @PostMapping 52 | public BookResponse createBook(@Valid @RequestBody CreateBookRequest createBookRequest) { 53 | Book book = Book.from(createBookRequest); 54 | book = bookRepository.save(book); 55 | return BookResponse.from(book); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/book-service/src/main/java/com/ivanfranchin/bookservice/book/BookRepository.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.bookservice.book; 2 | 3 | import com.ivanfranchin.bookservice.book.model.Book; 4 | import org.springframework.data.cassandra.repository.CassandraRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.UUID; 8 | 9 | @Repository 10 | public interface BookRepository extends CassandraRepository { 11 | } 12 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/book-service/src/main/java/com/ivanfranchin/bookservice/book/dto/BookResponse.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.bookservice.book.dto; 2 | 3 | import com.ivanfranchin.bookservice.book.model.Book; 4 | 5 | public record BookResponse(String id, String title, String author) { 6 | 7 | public static BookResponse from(Book book) { 8 | return new BookResponse(book.getId().toString(), book.getTitle(), book.getAuthor()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/book-service/src/main/java/com/ivanfranchin/bookservice/book/dto/CreateBookRequest.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.bookservice.book.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | public record CreateBookRequest( 7 | @Schema(example = "Spring Boot in a Nutshell") @NotBlank String title, 8 | @Schema(example = "Ivan Franchin") @NotBlank String author) { 9 | } 10 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/book-service/src/main/java/com/ivanfranchin/bookservice/book/model/Book.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.bookservice.book.model; 2 | 3 | import com.ivanfranchin.bookservice.book.dto.CreateBookRequest; 4 | import org.springframework.data.cassandra.core.mapping.PrimaryKey; 5 | import org.springframework.data.cassandra.core.mapping.Table; 6 | 7 | import java.util.UUID; 8 | 9 | @Table("books") 10 | public class Book { 11 | 12 | @PrimaryKey 13 | private UUID id; 14 | private String title; 15 | private String author; 16 | 17 | public Book() { 18 | } 19 | 20 | public Book(UUID id, String title, String author) { 21 | this.id = id; 22 | this.title = title; 23 | this.author = author; 24 | } 25 | 26 | public UUID getId() { 27 | return id; 28 | } 29 | 30 | public void setId(UUID id) { 31 | this.id = id; 32 | } 33 | 34 | public String getTitle() { 35 | return title; 36 | } 37 | 38 | public void setTitle(String title) { 39 | this.title = title; 40 | } 41 | 42 | public String getAuthor() { 43 | return author; 44 | } 45 | 46 | public void setAuthor(String author) { 47 | this.author = author; 48 | } 49 | 50 | public static Book from(CreateBookRequest createBookRequest) { 51 | return new Book(UUID.randomUUID(), createBookRequest.title(), createBookRequest.author()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/book-service/src/main/java/com/ivanfranchin/bookservice/config/CassandraConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.bookservice.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.cassandra.config.CqlSessionFactoryBean; 7 | import org.springframework.data.cassandra.core.cql.keyspace.CreateKeyspaceSpecification; 8 | import org.springframework.data.cassandra.core.cql.keyspace.KeyspaceOption; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | @Configuration 14 | public class CassandraConfig { 15 | 16 | @Value("${spring.cassandra.local-datacenter}") 17 | private String localDatacenter; 18 | 19 | @Value("${spring.cassandra.contact-points}") 20 | private String contactPoints; 21 | 22 | @Value("${spring.cassandra.port}") 23 | private int port; 24 | 25 | @Value("${spring.cassandra.keyspace-name}") 26 | private String keyspaceName; 27 | 28 | @Value("${spring.datasource.username}") 29 | private String username; 30 | 31 | @Value("${spring.datasource.password}") 32 | private String password; 33 | 34 | @Bean 35 | CqlSessionFactoryBean session() { 36 | CqlSessionFactoryBean session = new CqlSessionFactoryBean(); 37 | session.setContactPoints(contactPoints); 38 | session.setPort(port); 39 | session.setLocalDatacenter(localDatacenter); 40 | session.setKeyspaceName(keyspaceName); 41 | session.setKeyspaceCreations(getKeyspaceCreations()); 42 | session.setUsername(username); 43 | session.setPassword(password); 44 | return session; 45 | } 46 | 47 | 48 | private List getKeyspaceCreations() { 49 | final CreateKeyspaceSpecification specification = 50 | CreateKeyspaceSpecification.createKeyspace(keyspaceName) 51 | .ifNotExists() 52 | .with(KeyspaceOption.DURABLE_WRITES, true) 53 | .withSimpleReplication(); 54 | return Collections.singletonList(specification); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/book-service/src/main/java/com/ivanfranchin/bookservice/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.bookservice.config; 2 | 3 | import io.swagger.v3.oas.models.Components; 4 | import io.swagger.v3.oas.models.OpenAPI; 5 | import io.swagger.v3.oas.models.info.Info; 6 | import org.springdoc.core.models.GroupedOpenApi; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | public class SwaggerConfig { 13 | 14 | @Value("${spring.application.name}") 15 | private String applicationName; 16 | 17 | @Bean 18 | OpenAPI customOpenAPI() { 19 | return new OpenAPI().components(new Components()).info(new Info().title(applicationName)); 20 | } 21 | 22 | @Bean 23 | GroupedOpenApi customApi() { 24 | return GroupedOpenApi.builder().group("api").pathsToMatch("/api/**").build(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/book-service/src/main/java/com/ivanfranchin/bookservice/config/VaultLeaseConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.bookservice.config; 2 | 3 | import jakarta.annotation.PostConstruct; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.vault.core.lease.SecretLeaseContainer; 9 | import org.springframework.vault.core.lease.domain.RequestedSecret; 10 | import org.springframework.vault.core.lease.event.SecretLeaseCreatedEvent; 11 | import org.springframework.vault.core.lease.event.SecretLeaseExpiredEvent; 12 | 13 | @Configuration 14 | public class VaultLeaseConfig { 15 | 16 | private static final Logger log = LoggerFactory.getLogger(VaultLeaseConfig.class); 17 | 18 | private final SecretLeaseContainer leaseContainer; 19 | 20 | public VaultLeaseConfig(SecretLeaseContainer leaseContainer) { 21 | this.leaseContainer = leaseContainer; 22 | } 23 | 24 | @Value("${spring.cloud.vault.database.role}") 25 | private String databaseRole; 26 | 27 | @PostConstruct 28 | private void postConstruct() { 29 | final String vaultCredsPath = String.format("database/creds/%s", databaseRole); 30 | 31 | leaseContainer.addLeaseListener(event -> { 32 | log.info("==> Received event: {}", event); 33 | 34 | if (vaultCredsPath.equals(event.getSource().getPath())) { 35 | if (event instanceof SecretLeaseExpiredEvent && event.getSource().getMode() == RequestedSecret.Mode.RENEW) { 36 | log.info("==> Replace RENEW lease by a ROTATE one."); 37 | leaseContainer.requestRotatingSecret(vaultCredsPath); 38 | } else if (event instanceof SecretLeaseCreatedEvent secretLeaseCreatedEvent 39 | && event.getSource().getMode() == RequestedSecret.Mode.ROTATE) { 40 | String username = (String) secretLeaseCreatedEvent.getSecrets().get("username"); 41 | String password = (String) secretLeaseCreatedEvent.getSecrets().get("password"); 42 | 43 | log.info("==> Update System properties username & password"); 44 | System.setProperty("spring.datasource.username", username); 45 | System.setProperty("spring.datasource.password", password); 46 | 47 | log.info("==> spring.datasource.username: {}", username); 48 | 49 | updateDataSource(username, password); 50 | } 51 | } 52 | }); 53 | } 54 | 55 | private void updateDataSource(String username, String password) { 56 | // TODO 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/book-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cassandra: 3 | keyspace-name: mycompany 4 | contact-points: ${CASSANDRA_HOST:localhost} 5 | local-datacenter: datacenter1 6 | port: ${CASSANDRA_PORT:9042} 7 | schema-action: CREATE_IF_NOT_EXISTS 8 | request: 9 | timeout: 10s 10 | cloud: 11 | consul: 12 | host: ${CONSUL_HOST:localhost} 13 | port: ${CONSUL_PORT:8500} 14 | 15 | springdoc: 16 | swagger-ui: 17 | disable-swagger-default-url: true 18 | 19 | logging: 20 | level: 21 | org.springframework.vault: DEBUG 22 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/book-service/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | _ _ _ 2 | | |__ ___ ___ | | __ ___ ___ _ ____ _(_) ___ ___ 3 | | '_ \ / _ \ / _ \| |/ /____/ __|/ _ \ '__\ \ / / |/ __/ _ \ 4 | | |_) | (_) | (_) | <_____\__ \ __/ | \ V /| | (_| __/ 5 | |_.__/ \___/ \___/|_|\_\ |___/\___|_| \_/ |_|\___\___| 6 | :: Spring Boot :: ${spring-boot.formatted-version} 7 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/book-service/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: book-service 4 | cloud: 5 | vault: 6 | authentication: APPROLE 7 | app-role: 8 | role-id: book-service-role-id 9 | scheme: http 10 | host: ${VAULT_HOST:localhost} 11 | port: ${VAULT_PORT:8200} 12 | generic: 13 | enabled: true 14 | database: 15 | enabled: true 16 | role: book-role 17 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/book-service/src/test/java/com/ivanfranchin/bookservice/BookServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.bookservice; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @Disabled 8 | @SpringBootTest 9 | class BookServiceApplicationTests { 10 | 11 | @Test 12 | void contextLoads() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-cassandra/simulate-get-books.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | while true 4 | do 5 | curl -I http://localhost:9081/api/books 6 | sleep 1 7 | done -------------------------------------------------------------------------------- /spring-cloud-vault-approle-mysql/README.md: -------------------------------------------------------------------------------- 1 | # springboot-vault-examples 2 | ## `> spring-cloud-vault-approle-mysql` 3 | 4 | ## Project Diagram 5 | 6 | ![project-diagram](../documentation/spring-cloud-vault-approle-mysql.jpeg) 7 | 8 | ## Application 9 | 10 | - ### student-service 11 | 12 | - [`Spring Boot`](https://docs.spring.io/spring-boot/index.html) application that manages `students` 13 | - It uses [`MySQL`](https://www.mysql.com/) database as storage 14 | - It uses [`Spring Cloud Vault`](https://cloud.spring.io/spring-cloud-vault/reference/html/) 15 | - It uses [`Hikari`](https://github.com/brettwooldridge/HikariCP) JDBC connection pool 16 | - Credentials to access `MySQL` is generated dynamically by [`Vault`](https://www.vaultproject.io) 17 | - **Leases are renewed and rotated** 18 | - `AppRole` is the `Vault` authentication method used 19 | 20 | ## Prerequisite 21 | 22 | Before running this example, make sure the environment is initialized (see [Initialize Environment](https://github.com/ivangfr/springboot-vault-examples#initialize-environment) section in the main README). 23 | 24 | ## Start student-service 25 | 26 | ### Running with Maven Wrapper 27 | 28 | - In a terminal, make sure you are inside the `springboot-vault-examples` root folder; 29 | 30 | - Run the following command: 31 | ``` 32 | ./mvnw clean spring-boot:run \ 33 | --projects spring-cloud-vault-approle-mysql/student-service \ 34 | -Dspring-boot.run.jvmArguments="-Dserver.port=9080" 35 | ``` 36 | 37 | ### Running as Docker Container 38 | 39 | - In a terminal, make sure you are inside the `springboot-vault-examples` root folder; 40 | 41 | - Build the Docker image: 42 | ``` 43 | ./build-docker-images.sh spring-cloud-vault-approle-mysql 44 | ``` 45 | | Environment Variable | Description | 46 | |----------------------|-----------------------------------------------------------| 47 | | `VAULT_HOST` | Specify host of the `Vault` to use (default `localhost`) | 48 | | `VAULT_PORT` | Specify port of the `Vault` to use (default `8200`) | 49 | | `CONSUL_HOST` | Specify host of the `Consul` to use (default `localhost`) | 50 | | `CONSUL_PORT` | Specify port of the `Consul` to use (default `8500`) | 51 | | `MYSQL_HOST` | Specify host of the `MySQL` to use (default `localhost`) | 52 | | `MYSQL_PORT` | Specify port of the `MySQL` to use (default `3306`) | 53 | 54 | - Run the Docker container: 55 | ``` 56 | docker run --rm --name student-service -p 9080:8080 \ 57 | -e VAULT_HOST=vault -e CONSUL_HOST=consul -e MYSQL_HOST=mysql \ 58 | --network springboot-vault-examples \ 59 | ivanfranchin/student-service:1.0.0 60 | ``` 61 | 62 | ## Using student-service 63 | 64 | You can access `student-service` Swagger website at http://localhost:9080/swagger-ui.html 65 | 66 | ## Useful Links & Commands 67 | 68 | - **Vault** 69 | 70 | > **Note**: In order to run some commands, you must have [`jq`](https://jqlang.github.io/jq/) installed in your machine. 71 | 72 | - Open a new terminal 73 | 74 | - Set to `VAULT_ROOT_TOKEN` environment variable the value obtained while [initializing the environment](https://github.com/ivangfr/springboot-vault-examples#initialize-environment) described in the main README 75 | ``` 76 | VAULT_ROOT_TOKEN=... 77 | ``` 78 | 79 | - List of active leases for `database/creds/student-role` 80 | ``` 81 | curl -s -X LIST \ 82 | -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" \ 83 | http://localhost:8200/v1/sys/leases/lookup/database/creds/student-role | jq . 84 | ``` 85 | 86 | The response will be something like: 87 | ``` 88 | { 89 | "request_id": "ef6db810-2431-3d86-41fe-edd72b01356c", 90 | "lease_id": "", 91 | "renewable": false, 92 | "lease_duration": 0, 93 | "data": { 94 | "keys": [ 95 | "2RXJje2dzEOgClBVAO4O9dbx" 96 | ] 97 | }, 98 | "wrap_info": null, 99 | "warnings": null, 100 | "auth": null 101 | } 102 | ``` 103 | 104 | - See specific lease metadata 105 | ``` 106 | curl -s -X PUT \ 107 | -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" \ 108 | -d '{ "lease_id": "database/creds/student-role/2RXJje2dzEOgClBVAO4O9dbx" }' \ 109 | http://localhost:8200/v1/sys/leases/lookup | jq . 110 | ``` 111 | 112 | The response will be something like: 113 | ``` 114 | { 115 | "request_id": "51c08c58-4151-50e7-a757-f9e7f23addb5", 116 | "lease_id": "", 117 | "renewable": false, 118 | "lease_duration": 0, 119 | "data": { 120 | "expire_time": "2019-07-26T20:20:41.2821972Z", 121 | "id": "database/creds/student-role/2RXJje2dzEOgClBVAO4O9dbx", 122 | "issue_time": "2019-07-26T20:18:41.2821799Z", 123 | "last_renewal": null, 124 | "renewable": true, 125 | "ttl": 28 126 | }, 127 | "wrap_info": null, 128 | "warnings": null, 129 | "auth": null 130 | } 131 | ``` 132 | 133 | - **MySQL** 134 | 135 | - Open a new terminal 136 | 137 | - Connect to `MySQL monitor` inside docker container 138 | ``` 139 | docker exec -it -e MYSQL_PWD=secret mysql mysql -uroot 140 | ``` 141 | > To exit `MySQL monitor`, type `exit` 142 | 143 | - List users 144 | ``` 145 | SELECT User, Host FROM mysql.user; 146 | ``` 147 | 148 | - Show running process 149 | ``` 150 | SELECT * FROM information_schema.processlist ORDER BY user; 151 | ``` 152 | 153 | - Log all queries 154 | ``` 155 | SET GLOBAL general_log = 'ON'; 156 | SET GLOBAL log_output = 'table'; 157 | 158 | SELECT event_time, SUBSTRING(user_host,1,20) as user_host, thread_id, command_type, SUBSTRING(convert(argument using utf8),1,70) FROM mysql.general_log WHERE user_host LIKE 'v-approle-student-%'; 159 | ``` 160 | 161 | - Create/Remove user 162 | ``` 163 | CREATE USER 'newuser'@'%' IDENTIFIED BY 'password'; 164 | GRANT ALL PRIVILEGES ON * . * TO 'newuser'@'%'; 165 | 166 | DROP USER 'newuser'@'%'; 167 | ``` 168 | 169 | - **Consul** 170 | 171 | `Consul` can be accessed at http://localhost:8500 172 | 173 | ## Shutdown 174 | 175 | - Go to the terminal where the application is running and pressing `Ctrl+C`; 176 | - Stop the services started using the `init-environment` script as explained in [Shutdown Environment](https://github.com/ivangfr/springboot-vault-examples#shutdown-environment) section of the main README. 177 | 178 | ## Cleanup 179 | 180 | To remove the Docker image create by this example, go to a terminal and run the command below: 181 | ``` 182 | ./remove-docker-images.sh spring-cloud-vault-approle-mysql 183 | ``` 184 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-mysql/simulate-get-students.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | while true 4 | do 5 | curl -I http://localhost:9080/api/students 6 | sleep 1 7 | done -------------------------------------------------------------------------------- /spring-cloud-vault-approle-mysql/student-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.ivanfranchin 7 | springboot-vault-examples 8 | 1.0.0 9 | ../../pom.xml 10 | 11 | student-service 12 | student-service 13 | Demo project for Spring Boot 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 2024.0.0 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-data-jpa 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-web 38 | 39 | 40 | org.springframework.cloud 41 | spring-cloud-starter-bootstrap 42 | 43 | 44 | org.springframework.cloud 45 | spring-cloud-starter-consul-discovery 46 | 47 | 48 | org.springframework.cloud 49 | spring-cloud-vault-config-databases 50 | 51 | 52 | 53 | com.mysql 54 | mysql-connector-j 55 | runtime 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-test 60 | test 61 | 62 | 63 | 64 | 65 | 66 | org.springframework.cloud 67 | spring-cloud-dependencies 68 | ${spring-cloud.version} 69 | pom 70 | import 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | org.springframework.boot 79 | spring-boot-maven-plugin 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-mysql/student-service/src/main/java/com/ivanfranchin/studentservice/StudentServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.studentservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | 7 | @EnableDiscoveryClient 8 | @SpringBootApplication 9 | public class StudentServiceApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(StudentServiceApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-mysql/student-service/src/main/java/com/ivanfranchin/studentservice/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.studentservice.config; 2 | 3 | import io.swagger.v3.oas.models.Components; 4 | import io.swagger.v3.oas.models.OpenAPI; 5 | import io.swagger.v3.oas.models.info.Info; 6 | import org.springdoc.core.models.GroupedOpenApi; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | public class SwaggerConfig { 13 | 14 | @Value("${spring.application.name}") 15 | private String applicationName; 16 | 17 | @Bean 18 | OpenAPI customOpenAPI() { 19 | return new OpenAPI().components(new Components()).info(new Info().title(applicationName)); 20 | } 21 | 22 | @Bean 23 | GroupedOpenApi customApi() { 24 | return GroupedOpenApi.builder().group("api").pathsToMatch("/api/**").build(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-mysql/student-service/src/main/java/com/ivanfranchin/studentservice/config/VaultLeaseConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.studentservice.config; 2 | 3 | import com.zaxxer.hikari.HikariConfigMXBean; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import com.zaxxer.hikari.HikariPoolMXBean; 6 | import jakarta.annotation.PostConstruct; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.context.ApplicationContext; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.vault.core.lease.SecretLeaseContainer; 13 | import org.springframework.vault.core.lease.domain.RequestedSecret; 14 | import org.springframework.vault.core.lease.event.SecretLeaseCreatedEvent; 15 | import org.springframework.vault.core.lease.event.SecretLeaseExpiredEvent; 16 | 17 | @Configuration 18 | public class VaultLeaseConfig { 19 | 20 | private static final Logger log = LoggerFactory.getLogger(VaultLeaseConfig.class); 21 | 22 | private final ApplicationContext applicationContext; 23 | private final SecretLeaseContainer leaseContainer; 24 | 25 | public VaultLeaseConfig(ApplicationContext applicationContext, SecretLeaseContainer leaseContainer) { 26 | this.applicationContext = applicationContext; 27 | this.leaseContainer = leaseContainer; 28 | } 29 | 30 | @Value("${spring.cloud.vault.database.role}") 31 | private String databaseRole; 32 | 33 | @PostConstruct 34 | private void postConstruct() { 35 | final String vaultCredsPath = String.format("database/creds/%s", databaseRole); 36 | 37 | leaseContainer.addLeaseListener(event -> { 38 | log.info("==> Received event: {}", event); 39 | 40 | if (vaultCredsPath.equals(event.getSource().getPath())) { 41 | if (event instanceof SecretLeaseExpiredEvent && event.getSource().getMode() == RequestedSecret.Mode.RENEW) { 42 | log.info("==> Replace RENEW lease by a ROTATE one."); 43 | leaseContainer.requestRotatingSecret(vaultCredsPath); 44 | } else if (event instanceof SecretLeaseCreatedEvent secretLeaseCreatedEvent 45 | && event.getSource().getMode() == RequestedSecret.Mode.ROTATE) { 46 | String username = (String) secretLeaseCreatedEvent.getSecrets().get("username"); 47 | String password = (String) secretLeaseCreatedEvent.getSecrets().get("password"); 48 | 49 | log.info("==> Update System properties username & password"); 50 | System.setProperty("spring.datasource.username", username); 51 | System.setProperty("spring.datasource.password", password); 52 | 53 | log.info("==> spring.datasource.username: {}", username); 54 | 55 | updateDataSource(username, password); 56 | } 57 | } 58 | }); 59 | } 60 | 61 | private void updateDataSource(String username, String password) { 62 | HikariDataSource hikariDataSource = (HikariDataSource) applicationContext.getBean("dataSource"); 63 | 64 | log.info("==> Soft evict database connections"); 65 | HikariPoolMXBean hikariPoolMXBean = hikariDataSource.getHikariPoolMXBean(); 66 | if (hikariPoolMXBean != null) { 67 | hikariPoolMXBean.softEvictConnections(); 68 | } 69 | 70 | log.info("==> Update database credentials"); 71 | HikariConfigMXBean hikariConfigMXBean = hikariDataSource.getHikariConfigMXBean(); 72 | hikariConfigMXBean.setUsername(username); 73 | hikariConfigMXBean.setPassword(password); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-mysql/student-service/src/main/java/com/ivanfranchin/studentservice/student/StudentController.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.studentservice.student; 2 | 3 | import com.ivanfranchin.studentservice.student.model.Student; 4 | import com.ivanfranchin.studentservice.student.dto.CreateStudentRequest; 5 | import com.ivanfranchin.studentservice.student.dto.StudentResponse; 6 | import jakarta.validation.Valid; 7 | import org.springframework.core.env.Environment; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.ResponseStatus; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | import java.util.List; 17 | 18 | @RestController 19 | @RequestMapping("/api/students") 20 | public class StudentController { 21 | 22 | private final StudentRepository studentRepository; 23 | private final Environment environment; 24 | 25 | public StudentController(StudentRepository studentRepository, Environment environment) { 26 | this.studentRepository = studentRepository; 27 | this.environment = environment; 28 | } 29 | 30 | @GetMapping("/dbcredentials") 31 | public String getDBCredentials() { 32 | return String.format("%s/%s", 33 | environment.getProperty("spring.datasource.username"), 34 | environment.getProperty("spring.datasource.password")); 35 | } 36 | 37 | @GetMapping("/secretMessage") 38 | public String getSecretMessage() { 39 | return environment.getProperty("message"); 40 | } 41 | 42 | @GetMapping 43 | public List getStudents() { 44 | return studentRepository.findAll() 45 | .stream() 46 | .map(StudentResponse::from) 47 | .toList(); 48 | } 49 | 50 | @ResponseStatus(HttpStatus.CREATED) 51 | @PostMapping 52 | public StudentResponse createStudent(@Valid @RequestBody CreateStudentRequest createStudentRequest) { 53 | Student student = studentRepository.save(Student.from(createStudentRequest)); 54 | return StudentResponse.from(student); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-mysql/student-service/src/main/java/com/ivanfranchin/studentservice/student/StudentRepository.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.studentservice.student; 2 | 3 | import com.ivanfranchin.studentservice.student.model.Student; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface StudentRepository extends JpaRepository { 9 | } 10 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-mysql/student-service/src/main/java/com/ivanfranchin/studentservice/student/dto/CreateStudentRequest.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.studentservice.student.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | public record CreateStudentRequest( 7 | @Schema(example = "Ivan") @NotBlank String firstName, 8 | @Schema(example = "Franchin") @NotBlank String lastName, 9 | @Schema(example = "ivan.franchin@test.com") @NotBlank String email) { 10 | } 11 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-mysql/student-service/src/main/java/com/ivanfranchin/studentservice/student/dto/StudentResponse.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.studentservice.student.dto; 2 | 3 | import com.ivanfranchin.studentservice.student.model.Student; 4 | 5 | public record StudentResponse(Long id, String firstName, String lastName, String email) { 6 | 7 | public static StudentResponse from(Student student) { 8 | return new StudentResponse(student.getId(), student.getFirstName(), student.getLastName(), student.getEmail()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-mysql/student-service/src/main/java/com/ivanfranchin/studentservice/student/model/Student.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.studentservice.student.model; 2 | 3 | import com.ivanfranchin.studentservice.student.dto.CreateStudentRequest; 4 | import jakarta.persistence.Column; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.GeneratedValue; 7 | import jakarta.persistence.GenerationType; 8 | import jakarta.persistence.Id; 9 | import jakarta.persistence.Table; 10 | 11 | @Entity 12 | @Table(name = "students") 13 | public class Student { 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.IDENTITY) 17 | private Long id; 18 | 19 | @Column(nullable = false) 20 | private String firstName; 21 | 22 | @Column(nullable = false) 23 | private String lastName; 24 | 25 | @Column(nullable = false) 26 | private String email; 27 | 28 | public Student() { 29 | } 30 | 31 | public Student(String firstName, String lastName, String email) { 32 | this.firstName = firstName; 33 | this.lastName = lastName; 34 | this.email = email; 35 | } 36 | 37 | public Long getId() { 38 | return id; 39 | } 40 | 41 | public String getFirstName() { 42 | return firstName; 43 | } 44 | 45 | public void setFirstName(String firstName) { 46 | this.firstName = firstName; 47 | } 48 | 49 | public String getLastName() { 50 | return lastName; 51 | } 52 | 53 | public void setLastName(String lastName) { 54 | this.lastName = lastName; 55 | } 56 | 57 | public String getEmail() { 58 | return email; 59 | } 60 | 61 | public void setEmail(String email) { 62 | this.email = email; 63 | } 64 | 65 | public static Student from(CreateStudentRequest createStudentRequest) { 66 | return new Student(createStudentRequest.firstName(), createStudentRequest.lastName(), createStudentRequest.email()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-mysql/student-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | jpa: 3 | hibernate: 4 | ddl-auto: update 5 | show-sql: true 6 | datasource: 7 | url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/exampledb?characterEncoding=UTF-8&serverTimezone=UTC 8 | cloud: 9 | consul: 10 | host: ${CONSUL_HOST:localhost} 11 | port: ${CONSUL_PORT:8500} 12 | 13 | springdoc: 14 | swagger-ui: 15 | disable-swagger-default-url: true 16 | 17 | logging: 18 | level: 19 | org.springframework.vault: DEBUG -------------------------------------------------------------------------------- /spring-cloud-vault-approle-mysql/student-service/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | _ _ _ _ 2 | ___| |_ _ _ __| | ___ _ __ | |_ ___ ___ _ ____ _(_) ___ ___ 3 | / __| __| | | |/ _` |/ _ \ '_ \| __|____/ __|/ _ \ '__\ \ / / |/ __/ _ \ 4 | \__ \ |_| |_| | (_| | __/ | | | ||_____\__ \ __/ | \ V /| | (_| __/ 5 | |___/\__|\__,_|\__,_|\___|_| |_|\__| |___/\___|_| \_/ |_|\___\___| 6 | :: Spring Boot :: ${spring-boot.formatted-version} 7 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-mysql/student-service/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: student-service 4 | cloud: 5 | vault: 6 | authentication: APPROLE 7 | app-role: 8 | role-id: student-service-role-id 9 | scheme: http 10 | host: ${VAULT_HOST:localhost} 11 | port: ${VAULT_PORT:8200} 12 | generic: 13 | enabled: true 14 | database: 15 | enabled: true 16 | role: student-role 17 | -------------------------------------------------------------------------------- /spring-cloud-vault-approle-mysql/student-service/src/test/java/com/ivanfranchin/studentservice/StudentServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.studentservice; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @Disabled 8 | @SpringBootTest 9 | class StudentServiceApplicationTests { 10 | 11 | @Test 12 | void contextLoads() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/README.md: -------------------------------------------------------------------------------- 1 | # springboot-vault-examples 2 | ## `> spring-vault-approle-multi-datasources-mysql` 3 | 4 | ## Project Diagram 5 | 6 | ![project-diagram](../documentation/spring-vault-approle-multi-datasources-mysql.jpeg) 7 | 8 | ## Application 9 | 10 | - ### restaurant-service 11 | 12 | - [`Spring Boot`](https://docs.spring.io/spring-boot/index.html) application that manages `dishes` and `customers` 13 | - It uses [`MySQL`](https://www.mysql.com/) database as storage 14 | - It connects to two `MySQL` instances. One stores `dishes` information and another `customers` 15 | - It uses [`Spring Vault`](https://docs.spring.io/spring-vault/reference/) 16 | - It uses [`Hikari`](https://github.com/brettwooldridge/HikariCP) JDBC connection pool 17 | - Credentials to access `MySQL` is generated dynamically by [`Vault`](https://www.vaultproject.io) 18 | - **Leases are renewed and rotated** 19 | - `AppRole` is the `Vault` authentication method used 20 | 21 | ## Prerequisite 22 | 23 | Before running this example, make sure the environment is initialized (see [Initialize Environment](https://github.com/ivangfr/springboot-vault-examples#initialize-environment) section in the main README). 24 | 25 | ## Start restaurant-service 26 | 27 | ### Running with Maven Wrapper 28 | 29 | - In a terminal, make sure you are inside the `springboot-vault-examples` root folder; 30 | 31 | - Run the following commands: 32 | ``` 33 | export DISH_MYSQL_PORT=3307 && \ 34 | ./mvnw clean spring-boot:run \ 35 | --projects spring-vault-approle-multi-datasources-mysql/restaurant-service \ 36 | -Dspring-boot.run.jvmArguments="-Dserver.port=9083" 37 | ``` 38 | 39 | ### Running as Docker Container 40 | 41 | - In a terminal, make sure you are inside the `springboot-vault-examples` root folder; 42 | 43 | - Build the Docker image: 44 | ``` 45 | ./build-docker-images.sh spring-vault-approle-multi-datasources-mysql 46 | ``` 47 | | Environment Variable | Description | 48 | |-----------------------|-----------------------------------------------------------| 49 | | `VAULT_HOST` | Specify host of the `Vault` to use (default `localhost`) | 50 | | `VAULT_PORT` | Specify port of the `Vault` to use (default `8200`) | 51 | | `CONSUL_HOST` | Specify host of the `Consul` to use (default `localhost`) | 52 | | `CONSUL_PORT` | Specify port of the `Consul` to use (default `8500`) | 53 | | `CUSTOMER_MYSQL_HOST` | Specify host of the `MySQL` to use (default `localhost`) | 54 | | `CUSTOMER_MYSQL_PORT` | Specify port of the `MySQL` to use (default `3306`) | 55 | | `DISH_MYSQL_HOST` | Specify host of the `MySQL` to use (default `localhost`) | 56 | | `DISH_MYSQL_PORT` | Specify port of the `MySQL` to use (default `3306`) | 57 | 58 | - Run the Docker container 59 | ``` 60 | docker run --rm --name restaurant-service -p 9083:8080 \ 61 | -e VAULT_HOST=vault -e CONSUL_HOST=consul -e CUSTOMER_MYSQL_HOST=mysql -e DISH_MYSQL_HOST=mysql-2 \ 62 | --network springboot-vault-examples \ 63 | ivanfranchin/restaurant-service:1.0.0 64 | ``` 65 | 66 | ## Using restaurant-service 67 | 68 | You can access `restaurant-service` Swagger website at http://localhost:9083/swagger-ui.html 69 | 70 | ## Useful Links & Commands 71 | 72 | - **Vault** 73 | 74 | > **Note**: In order to run some commands, you must have [`jq`](https://jqlang.github.io/jq/) installed in your machine. 75 | 76 | - Open a new terminal 77 | 78 | - Set to `VAULT_ROOT_TOKEN` environment variable the value obtained while [initializing the environment](https://github.com/ivangfr/springboot-vault-examples#initialize-environment) described in the main README 79 | ``` 80 | VAULT_ROOT_TOKEN=... 81 | ``` 82 | 83 | - List of active leases for `database/creds/customer-role` (the same can be done for `database/creds/dish-role`) 84 | ``` 85 | curl -s -X LIST \ 86 | -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" \ 87 | http://localhost:8200/v1/sys/leases/lookup/database/creds/customer-role | jq . 88 | ``` 89 | 90 | The response will be something like: 91 | ``` 92 | { 93 | "request_id": "16d2ec27-bb75-29a4-a01d-489c4f7a2a34", 94 | "lease_id": "", 95 | "renewable": false, 96 | "lease_duration": 0, 97 | "data": { 98 | "keys": [ 99 | "4pUp7cHODXpdtsXadzgsL5aZ" 100 | ] 101 | }, 102 | "wrap_info": null, 103 | "warnings": null, 104 | "auth": null 105 | } 106 | ``` 107 | 108 | - See specific lease metadata 109 | ``` 110 | curl -s -X PUT \ 111 | -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" \ 112 | -d '{ "lease_id": "database/creds/customer-role/4pUp7cHODXpdtsXadzgsL5aZ" }' \ 113 | http://localhost:8200/v1/sys/leases/lookup | jq . 114 | ``` 115 | 116 | The response will be something like: 117 | ``` 118 | { 119 | "request_id": "c020ab73-84db-406f-2dac-d169d7f2db51", 120 | "lease_id": "", 121 | "renewable": false, 122 | "lease_duration": 0, 123 | "data": { 124 | "expire_time": "2019-10-05T22:22:32.4210957Z", 125 | "id": "database/creds/customer-role/4pUp7cHODXpdtsXadzgsL5aZ", 126 | "issue_time": "2019-10-05T22:19:32.3713442Z", 127 | "last_renewal": "2019-10-05T22:20:32.4211219Z", 128 | "renewable": true, 129 | "ttl": 78 130 | }, 131 | "wrap_info": null, 132 | "warnings": null, 133 | "auth": null 134 | } 135 | ``` 136 | 137 | - **MySQL** 138 | 139 | - Open a new terminal 140 | 141 | - Connect to `MySQL monitor` inside docker container 142 | ``` 143 | docker exec -it -e MYSQL_PWD=secret mysql mysql -uroot 144 | -- OR -- 145 | docker exec -it -e MYSQL_PWD=secret mysql-2 mysql -uroot 146 | ``` 147 | > To exit `MySQL monitor`, type `exit` 148 | 149 | - List users 150 | ``` 151 | SELECT User, Host FROM mysql.user; 152 | ``` 153 | 154 | - Show running process 155 | ``` 156 | SELECT * FROM information_schema.processlist ORDER BY user; 157 | ``` 158 | 159 | - Log all queries 160 | ``` 161 | SET GLOBAL general_log = 'ON'; 162 | SET GLOBAL log_output = 'table'; 163 | 164 | SELECT event_time, SUBSTRING(user_host,1,20) as user_host, thread_id, command_type, SUBSTRING(convert(argument using utf8),1,70) FROM mysql.general_log WHERE user_host LIKE 'v-approle-customer-%'; 165 | ``` 166 | 167 | - Create/Remove user 168 | ``` 169 | CREATE USER 'newuser'@'%' IDENTIFIED BY 'password'; 170 | GRANT ALL PRIVILEGES ON * . * TO 'newuser'@'%'; 171 | 172 | DROP USER 'newuser'@'%'; 173 | ``` 174 | 175 | - **Consul** 176 | 177 | `Consul` can be accessed at http://localhost:8500 178 | 179 | ## Shutdown 180 | 181 | - Go to the terminal where the application is running and pressing `Ctrl+C`; 182 | - Stop the services started using the `init-environment` script as explained in [Shutdown Environment](https://github.com/ivangfr/springboot-vault-examples#shutdown-environment) section of the main README. 183 | 184 | ## Cleanup 185 | 186 | To remove the Docker image create by this example, go to a terminal and run the command below: 187 | ``` 188 | ./remove-docker-images.sh spring-vault-approle-multi-datasources-mysql 189 | ``` 190 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.ivanfranchin 7 | springboot-vault-examples 8 | 1.0.0 9 | ../../pom.xml 10 | 11 | restaurant-service 12 | restaurant-service 13 | Demo project for Spring Boot 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 3.1.2 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-data-jpa 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-validation 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-web 42 | 43 | 44 | 45 | org.springframework.vault 46 | spring-vault-core 47 | ${spring-vault.version} 48 | 49 | 50 | 51 | com.mysql 52 | mysql-connector-j 53 | runtime 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-test 58 | test 59 | 60 | 61 | 62 | 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-maven-plugin 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/RestaurantServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class RestaurantServiceApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(RestaurantServiceApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.config; 2 | 3 | import io.swagger.v3.oas.models.Components; 4 | import io.swagger.v3.oas.models.OpenAPI; 5 | import io.swagger.v3.oas.models.info.Info; 6 | import org.springdoc.core.models.GroupedOpenApi; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | public class SwaggerConfig { 13 | 14 | @Value("${spring.application.name}") 15 | private String applicationName; 16 | 17 | @Bean 18 | OpenAPI customOpenAPI() { 19 | return new OpenAPI().components(new Components()).info(new Info().title(applicationName)); 20 | } 21 | 22 | @Bean 23 | GroupedOpenApi customApi() { 24 | return GroupedOpenApi.builder().group("api").pathsToMatch("/api/**").build(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/config/VaultConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.vault.annotation.VaultPropertySource; 5 | import org.springframework.vault.annotation.VaultPropertySource.Renewal; 6 | import org.springframework.vault.authentication.AppRoleAuthentication; 7 | import org.springframework.vault.authentication.AppRoleAuthenticationOptions; 8 | import org.springframework.vault.authentication.ClientAuthentication; 9 | import org.springframework.vault.client.VaultEndpoint; 10 | import org.springframework.vault.config.AbstractVaultConfiguration; 11 | 12 | import java.net.URI; 13 | 14 | @Configuration 15 | @VaultPropertySource( 16 | value = "${datasource.customer.vault-creds-path}", 17 | propertyNamePrefix = "datasource.customer.", 18 | renewal = Renewal.ROTATE) 19 | @VaultPropertySource( 20 | value = "${datasource.dish.vault-creds-path}", 21 | propertyNamePrefix = "datasource.dish.", 22 | renewal = Renewal.ROTATE) 23 | @VaultPropertySource( 24 | value = "${app.vault-kv-secret-path}", 25 | propertyNamePrefix = "secret.restaurant-service.") 26 | public class VaultConfig extends AbstractVaultConfiguration { 27 | 28 | @Override 29 | public VaultEndpoint vaultEndpoint() { 30 | String uri = getEnvironment().getProperty("vault.uri"); 31 | if (uri == null) { 32 | throw new IllegalStateException(); 33 | } 34 | return VaultEndpoint.from(URI.create(uri)); 35 | } 36 | 37 | @Override 38 | public ClientAuthentication clientAuthentication() { 39 | String roleId = getEnvironment().getProperty("vault.app-role.role-id"); 40 | if (roleId == null) { 41 | throw new IllegalStateException(); 42 | } 43 | AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() 44 | .roleId(AppRoleAuthenticationOptions.RoleId.provided(roleId)) 45 | .build(); 46 | 47 | return new AppRoleAuthentication(options, restOperations()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/customer/CustomerController.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.customer; 2 | 3 | import com.ivanfranchin.restaurantservice.customer.model.Customer; 4 | import com.ivanfranchin.restaurantservice.customer.dto.CreateCustomerRequest; 5 | import com.ivanfranchin.restaurantservice.customer.dto.CustomerResponse; 6 | import jakarta.validation.Valid; 7 | import org.springframework.core.env.Environment; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.ResponseStatus; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | import java.util.List; 17 | 18 | @RestController 19 | @RequestMapping("/api/customers") 20 | public class CustomerController { 21 | 22 | private final CustomerRepository customerRepository; 23 | private final Environment environment; 24 | 25 | public CustomerController(CustomerRepository customerRepository, Environment environment) { 26 | this.customerRepository = customerRepository; 27 | this.environment = environment; 28 | } 29 | 30 | @GetMapping("/dbcredentials") 31 | public String getDBCredentials() { 32 | return String.format("%s/%s", 33 | environment.getProperty("datasource.customer.username"), 34 | environment.getProperty("datasource.customer.password")); 35 | } 36 | 37 | @GetMapping("/secretMessage") 38 | public String getSecretMessage() { 39 | return environment.getProperty("secret.restaurant-service.message"); 40 | } 41 | 42 | @GetMapping 43 | public List getCustomers() { 44 | return customerRepository.findAll() 45 | .stream() 46 | .map(CustomerResponse::from) 47 | .toList(); 48 | } 49 | 50 | @ResponseStatus(HttpStatus.CREATED) 51 | @PostMapping 52 | public CustomerResponse createCustomer(@Valid @RequestBody CreateCustomerRequest createCustomerRequest) { 53 | Customer customer = customerRepository.save(Customer.from(createCustomerRequest)); 54 | return CustomerResponse.from(customer); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/customer/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.customer; 2 | 3 | import com.ivanfranchin.restaurantservice.customer.model.Customer; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface CustomerRepository extends JpaRepository { 9 | } 10 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/customer/config/CustomerDbConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.customer.config; 2 | 3 | import com.ivanfranchin.restaurantservice.customer.model.Customer; 4 | import com.ivanfranchin.restaurantservice.customer.CustomerRepository; 5 | import jakarta.persistence.EntityManagerFactory; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Qualifier; 9 | import org.springframework.boot.context.properties.ConfigurationProperties; 10 | import org.springframework.boot.jdbc.DataSourceBuilder; 11 | import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.context.annotation.Primary; 15 | import org.springframework.core.env.Environment; 16 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 17 | import org.springframework.orm.jpa.JpaTransactionManager; 18 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 19 | import org.springframework.transaction.PlatformTransactionManager; 20 | import org.springframework.transaction.annotation.EnableTransactionManagement; 21 | 22 | import javax.sql.DataSource; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | @Configuration 27 | @EnableTransactionManagement 28 | @EnableJpaRepositories( 29 | entityManagerFactoryRef = "customerEntityManagerFactory", 30 | transactionManagerRef = "customerTransactionManager", 31 | basePackageClasses = CustomerRepository.class 32 | ) 33 | public class CustomerDbConfig { 34 | 35 | private static final Logger log = LoggerFactory.getLogger(CustomerDbConfig.class); 36 | 37 | private final Environment environment; 38 | 39 | public CustomerDbConfig(Environment environment) { 40 | this.environment = environment; 41 | } 42 | 43 | @Primary 44 | @Bean(name = "customerDataSource") 45 | @ConfigurationProperties("datasource.customer") 46 | DataSource dataSource() { 47 | String username = environment.getProperty("datasource.customer.username"); 48 | log.info("==> datasource.customer.username: {}", username); 49 | 50 | // jdbcUrl, username and password are set implicitly in the "create" below 51 | return DataSourceBuilder.create().build(); 52 | } 53 | 54 | @Primary 55 | @Bean(name = "customerEntityManagerFactory") 56 | LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, 57 | @Qualifier("customerDataSource") DataSource dataSource) { 58 | Map properties = new HashMap<>(); 59 | properties.put("hibernate.hbm2ddl.auto", environment.getProperty("spring.jpa.hibernate.ddl-auto")); 60 | properties.put("hibernate.show-sql", environment.getProperty("spring.jpa.show-sql")); 61 | 62 | return builder.dataSource(dataSource) 63 | .packages(Customer.class) 64 | .persistenceUnit("customer") 65 | .properties(properties) 66 | .build(); 67 | } 68 | 69 | @Primary 70 | @Bean(name = "customerTransactionManager") 71 | PlatformTransactionManager transactionManager( 72 | @Qualifier("customerEntityManagerFactory") EntityManagerFactory entityManagerFactory) { 73 | return new JpaTransactionManager(entityManagerFactory); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/customer/config/CustomerVaultLeaseConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.customer.config; 2 | 3 | import com.zaxxer.hikari.HikariConfigMXBean; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import com.zaxxer.hikari.HikariPoolMXBean; 6 | import jakarta.annotation.PostConstruct; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.context.ApplicationContext; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.core.env.Environment; 13 | import org.springframework.vault.core.lease.SecretLeaseContainer; 14 | import org.springframework.vault.core.lease.event.SecretLeaseCreatedEvent; 15 | 16 | @Configuration 17 | public class CustomerVaultLeaseConfig { 18 | 19 | private static final Logger log = LoggerFactory.getLogger(CustomerVaultLeaseConfig.class); 20 | 21 | private final ApplicationContext applicationContext; 22 | private final Environment environment; 23 | private final SecretLeaseContainer leaseContainer; 24 | 25 | public CustomerVaultLeaseConfig(ApplicationContext applicationContext, Environment environment, SecretLeaseContainer leaseContainer) { 26 | this.applicationContext = applicationContext; 27 | this.environment = environment; 28 | this.leaseContainer = leaseContainer; 29 | } 30 | 31 | @Value("${datasource.customer.vault-creds-path}") 32 | private String vaultCredsPath; 33 | 34 | @PostConstruct 35 | private void postConstruct() { 36 | leaseContainer.addLeaseListener(event -> { 37 | if (vaultCredsPath.equals(event.getSource().getPath())) { 38 | log.info("==> Received event: {}", event); 39 | 40 | if (event instanceof SecretLeaseCreatedEvent) { 41 | String username = environment.getProperty("datasource.customer.username"); 42 | String password = environment.getProperty("datasource.customer.password"); 43 | 44 | log.info("==> datasource.customer.username: {}", username); 45 | 46 | updateDataSource(username, password); 47 | } 48 | } 49 | }); 50 | } 51 | 52 | private void updateDataSource(String username, String password) { 53 | HikariDataSource hikariDataSource = (HikariDataSource) applicationContext.getBean("customerDataSource"); 54 | 55 | log.info("==> Soft evict database connections"); 56 | HikariPoolMXBean hikariPoolMXBean = hikariDataSource.getHikariPoolMXBean(); 57 | if (hikariPoolMXBean != null) { 58 | hikariPoolMXBean.softEvictConnections(); 59 | } 60 | 61 | log.info("==> Update database credentials"); 62 | HikariConfigMXBean hikariConfigMXBean = hikariDataSource.getHikariConfigMXBean(); 63 | hikariConfigMXBean.setUsername(username); 64 | hikariConfigMXBean.setPassword(password); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/customer/dto/CreateCustomerRequest.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.customer.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.Email; 5 | import jakarta.validation.constraints.NotBlank; 6 | 7 | public record CreateCustomerRequest( 8 | @Schema(example = "Ivan Franchin") @NotBlank String name, 9 | @Schema(example = "ivan.franchin@test.com") @Email String email) { 10 | } 11 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/customer/dto/CustomerResponse.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.customer.dto; 2 | 3 | import com.ivanfranchin.restaurantservice.customer.model.Customer; 4 | 5 | public record CustomerResponse(Long id, String name, String email) { 6 | 7 | public static CustomerResponse from(Customer customer) { 8 | return new CustomerResponse(customer.getId(), customer.getName(), customer.getEmail()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/customer/model/Customer.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.customer.model; 2 | 3 | import com.ivanfranchin.restaurantservice.customer.dto.CreateCustomerRequest; 4 | import jakarta.persistence.Column; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.GeneratedValue; 7 | import jakarta.persistence.GenerationType; 8 | import jakarta.persistence.Id; 9 | import jakarta.persistence.Table; 10 | 11 | @Entity 12 | @Table(name = "customers") 13 | public class Customer { 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.IDENTITY) 17 | private Long id; 18 | 19 | @Column(nullable = false) 20 | private String name; 21 | 22 | @Column(nullable = false) 23 | private String email; 24 | 25 | public Long getId() { 26 | return id; 27 | } 28 | 29 | public Customer() { 30 | } 31 | 32 | public Customer(String name, String email) { 33 | this.name = name; 34 | this.email = email; 35 | } 36 | 37 | public String getName() { 38 | return name; 39 | } 40 | 41 | public void setName(String name) { 42 | this.name = name; 43 | } 44 | 45 | public String getEmail() { 46 | return email; 47 | } 48 | 49 | public void setEmail(String email) { 50 | this.email = email; 51 | } 52 | 53 | public static Customer from(CreateCustomerRequest createCustomerRequest) { 54 | return new Customer(createCustomerRequest.name(), createCustomerRequest.email()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/dish/DishController.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.dish; 2 | 3 | import com.ivanfranchin.restaurantservice.dish.model.Dish; 4 | import com.ivanfranchin.restaurantservice.dish.dto.CreateDishRequest; 5 | import com.ivanfranchin.restaurantservice.dish.dto.DishResponse; 6 | import jakarta.validation.Valid; 7 | import org.springframework.core.env.Environment; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.ResponseStatus; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | import java.util.List; 17 | 18 | @RestController 19 | @RequestMapping("/api/dishes") 20 | public class DishController { 21 | 22 | private final DishRepository dishRepository; 23 | private final Environment environment; 24 | 25 | public DishController(DishRepository dishRepository, Environment environment) { 26 | this.dishRepository = dishRepository; 27 | this.environment = environment; 28 | } 29 | 30 | @GetMapping("/dbcredentials") 31 | public String getDBCredentials() { 32 | return String.format("%s/%s", 33 | environment.getProperty("datasource.dish.username"), 34 | environment.getProperty("datasource.dish.password")); 35 | } 36 | 37 | @GetMapping("/secretMessage") 38 | public String getSecretMessage() { 39 | return environment.getProperty("secret.restaurant-service.message"); 40 | } 41 | 42 | @GetMapping 43 | public List getDishes() { 44 | return dishRepository.findAll() 45 | .stream() 46 | .map(DishResponse::from) 47 | .toList(); 48 | } 49 | 50 | @ResponseStatus(HttpStatus.CREATED) 51 | @PostMapping 52 | public DishResponse createDish(@Valid @RequestBody CreateDishRequest createDishRequest) { 53 | Dish dish = dishRepository.save(Dish.from(createDishRequest)); 54 | return DishResponse.from(dish); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/dish/DishRepository.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.dish; 2 | 3 | import com.ivanfranchin.restaurantservice.dish.model.Dish; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface DishRepository extends JpaRepository { 9 | } 10 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/dish/config/DishDbConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.dish.config; 2 | 3 | import com.ivanfranchin.restaurantservice.dish.model.Dish; 4 | import com.ivanfranchin.restaurantservice.dish.DishRepository; 5 | import jakarta.persistence.EntityManagerFactory; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Qualifier; 9 | import org.springframework.boot.context.properties.ConfigurationProperties; 10 | import org.springframework.boot.jdbc.DataSourceBuilder; 11 | import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.core.env.Environment; 15 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 16 | import org.springframework.orm.jpa.JpaTransactionManager; 17 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 18 | import org.springframework.transaction.PlatformTransactionManager; 19 | import org.springframework.transaction.annotation.EnableTransactionManagement; 20 | 21 | import javax.sql.DataSource; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | 25 | @Configuration 26 | @EnableTransactionManagement 27 | @EnableJpaRepositories( 28 | entityManagerFactoryRef = "dishEntityManagerFactory", 29 | transactionManagerRef = "dishTransactionManager", 30 | basePackageClasses = DishRepository.class 31 | ) 32 | public class DishDbConfig { 33 | 34 | private static final Logger log = LoggerFactory.getLogger(DishDbConfig.class); 35 | 36 | private final Environment environment; 37 | 38 | public DishDbConfig(Environment environment) { 39 | this.environment = environment; 40 | } 41 | 42 | @Bean(name = "dishDataSource") 43 | @ConfigurationProperties("datasource.dish") 44 | DataSource dataSource() { 45 | String username = environment.getProperty("datasource.dish.username"); 46 | log.info("==> datasource.dish.username: {}", username); 47 | 48 | // jdbcUrl, username and password are set implicitly in the "create" below 49 | return DataSourceBuilder.create().build(); 50 | } 51 | 52 | @Bean(name = "dishEntityManagerFactory") 53 | LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, 54 | @Qualifier("dishDataSource") DataSource dataSource) { 55 | Map properties = new HashMap<>(); 56 | properties.put("hibernate.hbm2ddl.auto", environment.getProperty("spring.jpa.hibernate.ddl-auto")); 57 | properties.put("hibernate.show-sql", environment.getProperty("spring.jpa.show-sql")); 58 | 59 | return builder.dataSource(dataSource) 60 | .packages(Dish.class) 61 | .persistenceUnit("dish") 62 | .properties(properties) 63 | .build(); 64 | } 65 | 66 | @Bean(name = "dishTransactionManager") 67 | PlatformTransactionManager transactionManager( 68 | @Qualifier("dishEntityManagerFactory") EntityManagerFactory entityManagerFactory) { 69 | return new JpaTransactionManager(entityManagerFactory); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/dish/config/DishVaultLeaseConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.dish.config; 2 | 3 | import com.zaxxer.hikari.HikariConfigMXBean; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import com.zaxxer.hikari.HikariPoolMXBean; 6 | import jakarta.annotation.PostConstruct; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.context.ApplicationContext; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.core.env.Environment; 13 | import org.springframework.vault.core.lease.SecretLeaseContainer; 14 | import org.springframework.vault.core.lease.event.SecretLeaseCreatedEvent; 15 | 16 | @Configuration 17 | public class DishVaultLeaseConfig { 18 | 19 | private static final Logger log = LoggerFactory.getLogger(DishVaultLeaseConfig.class); 20 | 21 | private final ApplicationContext applicationContext; 22 | private final Environment environment; 23 | private final SecretLeaseContainer leaseContainer; 24 | 25 | public DishVaultLeaseConfig(ApplicationContext applicationContext, Environment environment, SecretLeaseContainer leaseContainer) { 26 | this.applicationContext = applicationContext; 27 | this.environment = environment; 28 | this.leaseContainer = leaseContainer; 29 | } 30 | 31 | @Value("${datasource.dish.vault-creds-path}") 32 | private String vaultCredsPath; 33 | 34 | @PostConstruct 35 | private void postConstruct() { 36 | leaseContainer.addLeaseListener(event -> { 37 | if (vaultCredsPath.equals(event.getSource().getPath())) { 38 | log.info("==> Received event: {}", event); 39 | 40 | if (event instanceof SecretLeaseCreatedEvent) { 41 | String username = environment.getProperty("datasource.dish.username"); 42 | String password = environment.getProperty("datasource.dish.password"); 43 | 44 | log.info("==> datasource.dish.username: {}", username); 45 | 46 | updateDataSource(username, password); 47 | } 48 | } 49 | }); 50 | } 51 | 52 | private void updateDataSource(String username, String password) { 53 | HikariDataSource hikariDataSource = (HikariDataSource) applicationContext.getBean("dishDataSource"); 54 | 55 | log.info("==> Soft evict database connections"); 56 | HikariPoolMXBean hikariPoolMXBean = hikariDataSource.getHikariPoolMXBean(); 57 | if (hikariPoolMXBean != null) { 58 | hikariPoolMXBean.softEvictConnections(); 59 | } 60 | 61 | log.info("==> Update database credentials"); 62 | HikariConfigMXBean hikariConfigMXBean = hikariDataSource.getHikariConfigMXBean(); 63 | hikariConfigMXBean.setUsername(username); 64 | hikariConfigMXBean.setPassword(password); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/dish/dto/CreateDishRequest.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.dish.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.NotNull; 6 | 7 | import java.math.BigDecimal; 8 | 9 | public record CreateDishRequest( 10 | @Schema(example = "Pizza 4 Cheese") @NotBlank String name, 11 | @Schema(example = "5.90") @NotNull BigDecimal price) { 12 | } 13 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/dish/dto/DishResponse.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.dish.dto; 2 | 3 | import com.ivanfranchin.restaurantservice.dish.model.Dish; 4 | 5 | import java.math.BigDecimal; 6 | 7 | public record DishResponse(Long id, String name, BigDecimal price) { 8 | 9 | public static DishResponse from(Dish dish) { 10 | return new DishResponse(dish.getId(), dish.getName(), dish.getPrice()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/java/com/ivanfranchin/restaurantservice/dish/model/Dish.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice.dish.model; 2 | 3 | import com.ivanfranchin.restaurantservice.dish.dto.CreateDishRequest; 4 | import jakarta.persistence.Column; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.GeneratedValue; 7 | import jakarta.persistence.GenerationType; 8 | import jakarta.persistence.Id; 9 | import jakarta.persistence.Table; 10 | 11 | import java.math.BigDecimal; 12 | 13 | @Entity 14 | @Table(name = "dishes") 15 | public class Dish { 16 | 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | private Long id; 20 | 21 | @Column(nullable = false) 22 | private String name; 23 | 24 | @Column(nullable = false) 25 | private BigDecimal price; 26 | 27 | public Dish() { 28 | } 29 | 30 | public Dish(String name, BigDecimal price) { 31 | this.name = name; 32 | this.price = price; 33 | } 34 | 35 | public Long getId() { 36 | return id; 37 | } 38 | 39 | public String getName() { 40 | return name; 41 | } 42 | 43 | public void setName(String name) { 44 | this.name = name; 45 | } 46 | 47 | public BigDecimal getPrice() { 48 | return price; 49 | } 50 | 51 | public void setPrice(BigDecimal price) { 52 | this.price = price; 53 | } 54 | 55 | public static Dish from(CreateDishRequest createDishRequest) { 56 | return new Dish(createDishRequest.name(), createDishRequest.price()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: restaurant-service 4 | jpa: 5 | hibernate: 6 | ddl-auto: update 7 | show-sql: true 8 | cloud: 9 | consul: 10 | host: ${CONSUL_HOST:localhost} 11 | port: ${CONSUL_PORT:8500} 12 | 13 | datasource: 14 | customer: 15 | jdbc-url: jdbc:mysql://${CUSTOMER_MYSQL_HOST:localhost}:${CUSTOMER_MYSQL_PORT:3306}/exampledb?characterEncoding=UTF-8&serverTimezone=UTC 16 | vault-creds-path: database/creds/customer-role 17 | dish: 18 | jdbc-url: jdbc:mysql://${DISH_MYSQL_HOST:localhost}:${DISH_MYSQL_PORT:3306}/exampledb?characterEncoding=UTF-8&serverTimezone=UTC 19 | vault-creds-path: database/creds/dish-role 20 | 21 | app: 22 | vault-kv-secret-path: secret/restaurant-service 23 | 24 | vault: 25 | uri: http://${VAULT_HOST:localhost}:${VAULT_POST:8200} 26 | app-role: 27 | role-id: restaurant-service-role-id 28 | 29 | springdoc: 30 | swagger-ui: 31 | disable-swagger-default-url: true 32 | 33 | logging: 34 | level: 35 | org.springframework.vault: DEBUG 36 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | _ _ _ 2 | _ __ ___ ___| |_ __ _ _ _ _ __ __ _ _ __ | |_ ___ ___ _ ____ _(_) ___ ___ 3 | | '__/ _ \/ __| __/ _` | | | | '__/ _` | '_ \| __|____/ __|/ _ \ '__\ \ / / |/ __/ _ \ 4 | | | | __/\__ \ || (_| | |_| | | | (_| | | | | ||_____\__ \ __/ | \ V /| | (_| __/ 5 | |_| \___||___/\__\__,_|\__,_|_| \__,_|_| |_|\__| |___/\___|_| \_/ |_|\___\___| 6 | :: Spring Boot :: ${spring-boot.formatted-version} 7 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/restaurant-service/src/test/java/com/ivanfranchin/restaurantservice/RestaurantServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.restaurantservice; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @Disabled 8 | @SpringBootTest 9 | class RestaurantServiceApplicationTests { 10 | 11 | @Test 12 | void contextLoads() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spring-vault-approle-multi-datasources-mysql/simulate-get-customers-dishes.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | while true 4 | do 5 | curl -I http://localhost:9083/api/customers & curl -I http://localhost:9083/api/dishes 6 | sleep 1 7 | done -------------------------------------------------------------------------------- /spring-vault-approle-mysql/README.md: -------------------------------------------------------------------------------- 1 | # springboot-vault-examples 2 | ## `> spring-vault-approle-mysql` 3 | 4 | ## Project Diagram 5 | 6 | ![project-diagram](../documentation/spring-vault-approle-mysql.jpeg) 7 | 8 | ## Application 9 | 10 | - ### movie-service 11 | 12 | - [`Spring Boot`](https://docs.spring.io/spring-boot/index.html) application that manages `movies` 13 | - It uses [`MySQL`](https://www.mysql.com/) database as storage 14 | - It uses [`Spring Vault`](https://docs.spring.io/spring-vault/reference/) 15 | - It uses [`Hikari`](https://github.com/brettwooldridge/HikariCP) JDBC connection pool 16 | - Credentials to access `MySQL` is generated dynamically by [`Vault`](https://www.vaultproject.io) 17 | - **Leases are renewed and rotated** 18 | - `AppRole` is the `Vault` authentication method used 19 | 20 | ## Prerequisite 21 | 22 | Before running this example, make sure the environment is initialized (see [Initialize Environment](https://github.com/ivangfr/springboot-vault-examples#initialize-environment) section in the main README). 23 | 24 | ## Start movie-service 25 | 26 | ### Running with Maven Wrapper 27 | 28 | - In a terminal, make sure you are inside the `springboot-vault-examples` root folder; 29 | 30 | - Run the following command: 31 | ``` 32 | ./mvnw clean spring-boot:run \ 33 | --projects spring-vault-approle-mysql/movie-service \ 34 | -Dspring-boot.run.jvmArguments="-Dserver.port=9082" 35 | ``` 36 | 37 | ### Running as Docker Container 38 | 39 | - In a terminal, make sure you are inside the `springboot-vault-examples` root folder; 40 | 41 | - Build the Docker image: 42 | ``` 43 | ./build-docker-images.sh spring-vault-approle-mysql 44 | ``` 45 | | Environment Variable | Description | 46 | |----------------------|-----------------------------------------------------------| 47 | | `VAULT_HOST` | Specify host of the `Vault` to use (default `localhost`) | 48 | | `VAULT_PORT` | Specify port of the `Vault` to use (default `8200`) | 49 | | `CONSUL_HOST` | Specify host of the `Consul` to use (default `localhost`) | 50 | | `CONSUL_PORT` | Specify port of the `Consul` to use (default `8500`) | 51 | | `MYSQL_HOST` | Specify host of the `MySQL` to use (default `localhost`) | 52 | | `MYSQL_PORT` | Specify port of the `MySQL` to use (default `3306`) | 53 | 54 | - Run the Docker container: 55 | ``` 56 | docker run --rm --name movie-service -p 9082:8080 \ 57 | -e VAULT_HOST=vault -e CONSUL_HOST=consul -e MYSQL_HOST=mysql \ 58 | --network springboot-vault-examples \ 59 | ivanfranchin/movie-service:1.0.0 60 | ``` 61 | 62 | ## Using movie-service 63 | 64 | You can access `movie-service` Swagger website at http://localhost:9082/swagger-ui.html 65 | 66 | ## Useful Links & Commands 67 | 68 | - **Vault** 69 | 70 | > **Note**: In order to run some commands, you must have [`jq`](https://jqlang.github.io/jq/) installed in your machine. 71 | 72 | - Open a new terminal 73 | 74 | - Set to `VAULT_ROOT_TOKEN` environment variable the value obtained while [initializing the environment](https://github.com/ivangfr/springboot-vault-examples#initialize-environment) described in the main README 75 | ``` 76 | VAULT_ROOT_TOKEN=... 77 | ``` 78 | 79 | - List of active leases for `database/creds/movie-role` 80 | ``` 81 | curl -s -X LIST \ 82 | -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" \ 83 | http://localhost:8200/v1/sys/leases/lookup/database/creds/movie-role | jq . 84 | ``` 85 | 86 | The response will be something like: 87 | ``` 88 | { 89 | "request_id": "5dbe3871-fc4d-a727-d77f-a4c9495e4d1a", 90 | "lease_id": "", 91 | "renewable": false, 92 | "lease_duration": 0, 93 | "data": { 94 | "keys": [ 95 | "CCV6h8U59TuGNNrvSUPNndYh" 96 | ] 97 | }, 98 | "wrap_info": null, 99 | "warnings": null, 100 | "auth": null 101 | } 102 | ``` 103 | 104 | - See specific lease metadata 105 | ``` 106 | curl -s -X PUT \ 107 | -H "X-Vault-Token: ${VAULT_ROOT_TOKEN}" \ 108 | -d '{ "lease_id": "database/creds/movie-role/CCV6h8U59TuGNNrvSUPNndYh" }' \ 109 | http://localhost:8200/v1/sys/leases/lookup | jq . 110 | ``` 111 | 112 | The response will be something like: 113 | ``` 114 | { 115 | "request_id": "38c6c6fb-74f1-dd31-e89e-7e8a00f0f1f4", 116 | "lease_id": "", 117 | "renewable": false, 118 | "lease_duration": 0, 119 | "data": { 120 | "expire_time": "2019-07-26T20:19:04.1710843Z", 121 | "id": "database/creds/movie-role/CCV6h8U59TuGNNrvSUPNndYh", 122 | "issue_time": "2019-07-26T20:17:04.1710687Z", 123 | "last_renewal": null, 124 | "renewable": true, 125 | "ttl": 66 126 | }, 127 | "wrap_info": null, 128 | "warnings": null, 129 | "auth": null 130 | } 131 | ``` 132 | 133 | - **MySQL** 134 | 135 | - Open a new terminal 136 | 137 | - Connect to `MySQL monitor` inside docker container 138 | ``` 139 | docker exec -it -e MYSQL_PWD=secret mysql mysql -uroot 140 | ``` 141 | > To exit `MySQL monitor`, type `exit` 142 | 143 | - List users 144 | ``` 145 | SELECT User, Host FROM mysql.user; 146 | ``` 147 | 148 | - Show running process 149 | ``` 150 | SELECT * FROM information_schema.processlist ORDER BY user; 151 | ``` 152 | 153 | - Log all queries 154 | ``` 155 | SET GLOBAL general_log = 'ON'; 156 | SET GLOBAL log_output = 'table'; 157 | 158 | SELECT event_time, SUBSTRING(user_host,1,20) as user_host, thread_id, command_type, SUBSTRING(convert(argument using utf8),1,70) FROM mysql.general_log WHERE user_host LIKE 'v-approle-movie-%'; 159 | ``` 160 | 161 | - Create/Remove user 162 | ``` 163 | CREATE USER 'newuser'@'%' IDENTIFIED BY 'password'; 164 | GRANT ALL PRIVILEGES ON * . * TO 'newuser'@'%'; 165 | 166 | DROP USER 'newuser'@'%'; 167 | ``` 168 | 169 | - **Consul** 170 | 171 | `Consul` can be accessed at http://localhost:8500 172 | 173 | ## Shutdown 174 | 175 | - Go to the terminal where the application is running and pressing `Ctrl+C`; 176 | - Stop the services started using the `init-environment` script as explained in [Shutdown Environment](https://github.com/ivangfr/springboot-vault-examples#shutdown-environment) section of the main README. 177 | 178 | ## Cleanup 179 | 180 | To remove the Docker image create by this example, go to a terminal and run the command below: 181 | ``` 182 | ./remove-docker-images.sh spring-vault-approle-mysql 183 | ``` 184 | -------------------------------------------------------------------------------- /spring-vault-approle-mysql/movie-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.ivanfranchin 7 | springboot-vault-examples 8 | 1.0.0 9 | ../../pom.xml 10 | 11 | movie-service 12 | movie-service 13 | Demo project for Spring Boot 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 3.1.2 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-data-jpa 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-validation 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-web 42 | 43 | 44 | 45 | org.springframework.vault 46 | spring-vault-core 47 | ${spring-vault.version} 48 | 49 | 50 | 51 | com.mysql 52 | mysql-connector-j 53 | runtime 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-test 58 | test 59 | 60 | 61 | 62 | 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-maven-plugin 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /spring-vault-approle-mysql/movie-service/src/main/java/com/ivanfranchin/movieservice/MovieServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.movieservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MovieServiceApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MovieServiceApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-vault-approle-mysql/movie-service/src/main/java/com/ivanfranchin/movieservice/config/DbConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.movieservice.config; 2 | 3 | import com.ivanfranchin.movieservice.movie.model.Movie; 4 | import com.ivanfranchin.movieservice.movie.MovieRepository; 5 | import jakarta.persistence.EntityManagerFactory; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.boot.context.properties.ConfigurationProperties; 9 | import org.springframework.boot.jdbc.DataSourceBuilder; 10 | import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.core.env.Environment; 14 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 15 | import org.springframework.orm.jpa.JpaTransactionManager; 16 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 17 | import org.springframework.transaction.PlatformTransactionManager; 18 | import org.springframework.transaction.annotation.EnableTransactionManagement; 19 | 20 | import javax.sql.DataSource; 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | 24 | @Configuration 25 | @EnableTransactionManagement 26 | @EnableJpaRepositories(basePackageClasses = MovieRepository.class) 27 | public class DbConfig { 28 | 29 | private static final Logger log = LoggerFactory.getLogger(DbConfig.class); 30 | 31 | private final Environment environment; 32 | 33 | public DbConfig(Environment environment) { 34 | this.environment = environment; 35 | } 36 | 37 | @Bean 38 | @ConfigurationProperties("datasource") 39 | DataSource dataSource() { 40 | String username = environment.getProperty("datasource.username"); 41 | log.info("==> datasource.username: {}", username); 42 | 43 | // jdbcUrl, username and password are set implicitly in the "create" below 44 | return DataSourceBuilder.create().build(); 45 | } 46 | 47 | @Bean 48 | LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, 49 | DataSource dataSource) { 50 | Map properties = new HashMap<>(); 51 | properties.put("hibernate.hbm2ddl.auto", environment.getProperty("spring.jpa.hibernate.ddl-auto")); 52 | properties.put("hibernate.show-sql", environment.getProperty("spring.jpa.show-sql")); 53 | 54 | return builder.dataSource(dataSource) 55 | .packages(Movie.class) 56 | .persistenceUnit("movie") 57 | .properties(properties) 58 | .build(); 59 | } 60 | 61 | @Bean 62 | PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { 63 | return new JpaTransactionManager(entityManagerFactory); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /spring-vault-approle-mysql/movie-service/src/main/java/com/ivanfranchin/movieservice/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.movieservice.config; 2 | 3 | import io.swagger.v3.oas.models.Components; 4 | import io.swagger.v3.oas.models.OpenAPI; 5 | import io.swagger.v3.oas.models.info.Info; 6 | import org.springdoc.core.models.GroupedOpenApi; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | public class SwaggerConfig { 13 | 14 | @Value("${spring.application.name}") 15 | private String applicationName; 16 | 17 | @Bean 18 | OpenAPI customOpenAPI() { 19 | return new OpenAPI().components(new Components()).info(new Info().title(applicationName)); 20 | } 21 | 22 | @Bean 23 | GroupedOpenApi customApi() { 24 | return GroupedOpenApi.builder().group("api").pathsToMatch("/api/**").build(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-vault-approle-mysql/movie-service/src/main/java/com/ivanfranchin/movieservice/config/VaultConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.movieservice.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.vault.annotation.VaultPropertySource; 5 | import org.springframework.vault.annotation.VaultPropertySource.Renewal; 6 | import org.springframework.vault.authentication.AppRoleAuthentication; 7 | import org.springframework.vault.authentication.AppRoleAuthenticationOptions; 8 | import org.springframework.vault.authentication.ClientAuthentication; 9 | import org.springframework.vault.client.VaultEndpoint; 10 | import org.springframework.vault.config.AbstractVaultConfiguration; 11 | 12 | import java.net.URI; 13 | 14 | @Configuration 15 | @VaultPropertySource( 16 | value = "${datasource.vault-creds-path}", 17 | propertyNamePrefix = "datasource.", 18 | renewal = Renewal.ROTATE) 19 | @VaultPropertySource( 20 | value = "${app.vault-kv-secret-path}", 21 | propertyNamePrefix = "secret.movie-service.") 22 | public class VaultConfig extends AbstractVaultConfiguration { 23 | 24 | @Override 25 | public VaultEndpoint vaultEndpoint() { 26 | String uri = getEnvironment().getProperty("vault.uri"); 27 | if (uri == null) { 28 | throw new IllegalStateException(); 29 | } 30 | return VaultEndpoint.from(URI.create(uri)); 31 | } 32 | 33 | @Override 34 | public ClientAuthentication clientAuthentication() { 35 | String roleId = getEnvironment().getProperty("vault.app-role.role-id"); 36 | if (roleId == null) { 37 | throw new IllegalStateException(); 38 | } 39 | AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder() 40 | .roleId(AppRoleAuthenticationOptions.RoleId.provided(roleId)) 41 | .build(); 42 | 43 | return new AppRoleAuthentication(options, restOperations()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /spring-vault-approle-mysql/movie-service/src/main/java/com/ivanfranchin/movieservice/config/VaultLeaseConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.movieservice.config; 2 | 3 | import com.zaxxer.hikari.HikariConfigMXBean; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import com.zaxxer.hikari.HikariPoolMXBean; 6 | import jakarta.annotation.PostConstruct; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.context.ApplicationContext; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.core.env.Environment; 13 | import org.springframework.vault.core.lease.SecretLeaseContainer; 14 | import org.springframework.vault.core.lease.event.SecretLeaseCreatedEvent; 15 | 16 | @Configuration 17 | public class VaultLeaseConfig { 18 | 19 | private static final Logger log = LoggerFactory.getLogger(VaultLeaseConfig.class); 20 | 21 | private final ApplicationContext applicationContext; 22 | private final Environment environment; 23 | private final SecretLeaseContainer leaseContainer; 24 | 25 | public VaultLeaseConfig(ApplicationContext applicationContext, Environment environment, SecretLeaseContainer leaseContainer) { 26 | this.applicationContext = applicationContext; 27 | this.environment = environment; 28 | this.leaseContainer = leaseContainer; 29 | } 30 | 31 | @Value("${datasource.vault-creds-path}") 32 | private String vaultCredsPath; 33 | 34 | @PostConstruct 35 | private void postConstruct() { 36 | leaseContainer.addLeaseListener(event -> { 37 | log.info("==> Received event: {}", event); 38 | 39 | if (event instanceof SecretLeaseCreatedEvent && vaultCredsPath.equals(event.getSource().getPath())) { 40 | String username = environment.getProperty("datasource.username"); 41 | String password = environment.getProperty("datasource.password"); 42 | 43 | log.info("==> datasource.username: {}", username); 44 | 45 | updateDataSource(username, password); 46 | } 47 | }); 48 | } 49 | 50 | private void updateDataSource(String username, String password) { 51 | HikariDataSource hikariDataSource = (HikariDataSource) applicationContext.getBean("dataSource"); 52 | 53 | log.info("==> Soft evict database connections"); 54 | HikariPoolMXBean hikariPoolMXBean = hikariDataSource.getHikariPoolMXBean(); 55 | if (hikariPoolMXBean != null) { 56 | hikariPoolMXBean.softEvictConnections(); 57 | } 58 | 59 | log.info("==> Update database credentials"); 60 | HikariConfigMXBean hikariConfigMXBean = hikariDataSource.getHikariConfigMXBean(); 61 | hikariConfigMXBean.setUsername(username); 62 | hikariConfigMXBean.setPassword(password); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /spring-vault-approle-mysql/movie-service/src/main/java/com/ivanfranchin/movieservice/movie/MovieController.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.movieservice.movie; 2 | 3 | import com.ivanfranchin.movieservice.movie.dto.CreateMovieRequest; 4 | import com.ivanfranchin.movieservice.movie.dto.MovieResponse; 5 | import com.ivanfranchin.movieservice.movie.model.Movie; 6 | import jakarta.validation.Valid; 7 | import org.springframework.core.env.Environment; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.ResponseStatus; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | import java.util.List; 17 | 18 | @RestController 19 | @RequestMapping("/api/movies") 20 | public class MovieController { 21 | 22 | private final MovieRepository movieRepository; 23 | private final Environment environment; 24 | 25 | public MovieController(MovieRepository movieRepository, Environment environment) { 26 | this.movieRepository = movieRepository; 27 | this.environment = environment; 28 | } 29 | 30 | @GetMapping("/dbcredentials") 31 | public String getDBCredentials() { 32 | return String.format("%s/%s", 33 | environment.getProperty("datasource.username"), 34 | environment.getProperty("datasource.password")); 35 | } 36 | 37 | @GetMapping("/secretMessage") 38 | public String getSecretMessage() { 39 | return environment.getProperty("secret.movie-service.message"); 40 | } 41 | 42 | @GetMapping 43 | public List getMovies() { 44 | return movieRepository.findAll() 45 | .stream() 46 | .map(MovieResponse::from) 47 | .toList(); 48 | } 49 | 50 | @ResponseStatus(HttpStatus.CREATED) 51 | @PostMapping 52 | public MovieResponse createMovie(@Valid @RequestBody CreateMovieRequest createMovieRequest) { 53 | Movie movie = movieRepository.save(Movie.from(createMovieRequest)); 54 | return MovieResponse.from(movie); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spring-vault-approle-mysql/movie-service/src/main/java/com/ivanfranchin/movieservice/movie/MovieRepository.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.movieservice.movie; 2 | 3 | import com.ivanfranchin.movieservice.movie.model.Movie; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface MovieRepository extends JpaRepository { 9 | } 10 | -------------------------------------------------------------------------------- /spring-vault-approle-mysql/movie-service/src/main/java/com/ivanfranchin/movieservice/movie/dto/CreateMovieRequest.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.movieservice.movie.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import jakarta.validation.constraints.NotBlank; 5 | 6 | public record CreateMovieRequest(@Schema(example = "Resident Evil") @NotBlank String title) { 7 | } 8 | -------------------------------------------------------------------------------- /spring-vault-approle-mysql/movie-service/src/main/java/com/ivanfranchin/movieservice/movie/dto/MovieResponse.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.movieservice.movie.dto; 2 | 3 | import com.ivanfranchin.movieservice.movie.model.Movie; 4 | 5 | public record MovieResponse(Long id, String title) { 6 | 7 | public static MovieResponse from(Movie movie) { 8 | return new MovieResponse(movie.getId(), movie.getTitle()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /spring-vault-approle-mysql/movie-service/src/main/java/com/ivanfranchin/movieservice/movie/model/Movie.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.movieservice.movie.model; 2 | 3 | import com.ivanfranchin.movieservice.movie.dto.CreateMovieRequest; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.GeneratedValue; 6 | import jakarta.persistence.GenerationType; 7 | import jakarta.persistence.Id; 8 | import jakarta.persistence.Table; 9 | 10 | @Entity 11 | @Table(name = "movies") 12 | public class Movie { 13 | 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.IDENTITY) 16 | private Long id; 17 | 18 | private String title; 19 | 20 | public Movie() { 21 | } 22 | 23 | public Movie(String title) { 24 | this.title = title; 25 | } 26 | 27 | public Long getId() { 28 | return id; 29 | } 30 | 31 | public String getTitle() { 32 | return title; 33 | } 34 | 35 | public void setTitle(String title) { 36 | this.title = title; 37 | } 38 | 39 | public static Movie from(CreateMovieRequest createMovieRequest) { 40 | return new Movie(createMovieRequest.title()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /spring-vault-approle-mysql/movie-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: movie-service 4 | jpa: 5 | hibernate: 6 | ddl-auto: update 7 | show-sql: true 8 | cloud: 9 | consul: 10 | host: ${CONSUL_HOST:localhost} 11 | port: ${CONSUL_PORT:8500} 12 | 13 | datasource: 14 | jdbc-url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/exampledb?characterEncoding=UTF-8&serverTimezone=UTC 15 | vault-creds-path: database/creds/movie-role 16 | 17 | app: 18 | vault-kv-secret-path: secret/movie-service 19 | 20 | vault: 21 | uri: http://${VAULT_HOST:localhost}:${VAULT_POST:8200} 22 | app-role: 23 | role-id: movie-service-role-id 24 | 25 | springdoc: 26 | swagger-ui: 27 | disable-swagger-default-url: true 28 | 29 | logging: 30 | level: 31 | org.springframework.vault: DEBUG 32 | -------------------------------------------------------------------------------- /spring-vault-approle-mysql/movie-service/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | _ _ 2 | _ __ ___ _____ _(_) ___ ___ ___ _ ____ _(_) ___ ___ 3 | | '_ ` _ \ / _ \ \ / / |/ _ \_____/ __|/ _ \ '__\ \ / / |/ __/ _ \ 4 | | | | | | | (_) \ V /| | __/_____\__ \ __/ | \ V /| | (_| __/ 5 | |_| |_| |_|\___/ \_/ |_|\___| |___/\___|_| \_/ |_|\___\___| 6 | :: Spring Boot :: ${spring-boot.formatted-version} 7 | -------------------------------------------------------------------------------- /spring-vault-approle-mysql/movie-service/src/test/java/com/ivanfranchin/movieservice/MovieServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.movieservice; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @Disabled 8 | @SpringBootTest 9 | class MovieServiceApplicationTests { 10 | 11 | @Test 12 | void contextLoads() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spring-vault-approle-mysql/simulate-get-movies.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | while true 4 | do 5 | curl -I http://localhost:9082/api/movies 6 | sleep 1 7 | done --------------------------------------------------------------------------------