From e0ccbbeb4abbe9b21c8a6928e3cc1600fa403d2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96z?= <153918634+ozozgun@users.noreply.github.com> Date: Sat, 2 Mar 2024 21:03:45 +0100 Subject: [PATCH] add tests for authentication configs via cli, add documentation for the same topic. (#18) * add tests for authentication configs via cli, add documentation. --------- Co-authored-by: Hatake Kakashi Co-authored-by: OZ --- docs/examples.markdown | 312 ------------------ docs/index.markdown | 6 +- docs/usage.markdown | 43 ++- docs/use-cases/appTypes.markdown | 2 + docs/use-cases/executionEnvs.markdown | 2 + .../vault/client/JOpenLibsVaultClient.java | 2 +- .../AuthenticationMethodFactory.java | 2 + .../vault/client/TestVaultClients.java | 18 +- .../TestGithubTokenAuthMethod.java | 36 ++ 9 files changed, 96 insertions(+), 327 deletions(-) delete mode 100644 docs/examples.markdown diff --git a/docs/examples.markdown b/docs/examples.markdown deleted file mode 100644 index b218d21..0000000 --- a/docs/examples.markdown +++ /dev/null @@ -1,312 +0,0 @@ ---- -layout: page -title: Practical use cases ---- - -# 1. Other maven plugins -Many CI tools are used via maven plugins for the convenience of not having to manage executables. -Sonar scanner and liquibase are one of them. -The example below shows you how to fetch the credentials necessary to use those plugins, via the vault-maven-plugin. - -Let's say you are using liquibase and have the following pom.xml -```xml - - - - org.liquibase - liquibase-maven-plugin - 4.6.1 - - - compile - - update - - - - - target/classes/liquibase.properties - - - - - - src/main/resources - true - - *.properties - - - - -``` - -Your property file might be like below. -```properties -changeLogFile=src/main/resources/liquibase/.../changelog.xml -url=... -database=... -username=... -password=${database.password} -``` - -Usually the property `database.password` if given to maven as system property or environment variables. - -If you don't have a tool that will fetch the secret and create the environment variable for you, you can use the maven vault plugin. -You just add the following to fetch the secrets and inject them as properties in maven. -```xml - - com.homeofthewizard - vault-maven-plugin - 1.1.2-SNAPSHOT - - - pull - initialize - - pull - - - - - - - https://your-vault-host - XXXXXXX - - - secret/data/some-app - - - vaultSecretKey - database.password - - - - - - - - -``` - -and VoilĂ ! :tada: -When you execute your liquibase plugin `mvn clean compile`, -the vault plugin will fetch the secrets, -the resource plugin will put it in the property file by replacing the placeholder, -then the liquibase plugin will run using the secret! - -No need to handle secrets in the execution environment anymore ! :massage_man: -You only need to give set the execution environment's credentials to access Vault! - -# 2. Maven Applications - -{: .warning } -As already mentionned [here](https://homeofthewizard.github.io/vault-maven-plugin/#example-use-cases), if your application is using Spring-Cloud, -You should use [Spring Cloud Vault](https://spring.io/projects/spring-cloud-vault) instead of this maven plugin. - -Many Java application like Spring applications externalise their configurations in a property file, -as recommended by the [12 factor app](https://12factor.net/config). -Secrets are part of those configuration files. The configuration file can be stored in version control. -But a good security practice is not to store the secrets on version control. -Hence, the secrets are fetched generally from environment variables on the system where we run the app. - -You can do the following to fetch the secrets from vault via the Vault Plugin, -then run your application with maven, and your secrets will be injected as system properties. - -Let's say you are using Spring and have the following `application.properties` -```properties -app.secret=${APPLICATION_SECRET} -``` - -You can define the following in your pom.xml to fetch the credential for `APPLICATION_SECRET`. -```xml - - - com.homeofthewizard - vault-maven-plugin - 1.1.2-SNAPSHOT - - - pull - initialize - - pull - - - - - - - https://your-vault-host - XXXXXXX - - - secret/data/some-app - - - vaultSecretKey - APPLICATION_SECRET - - - - - - - SystemProperties - - - -``` - -The above code will first fetch the secrets from vault with the vault-plugin, then they will be added to Maven's system properties (`` configuration). -After that you can run your java app with either [exec-maven-plugin](http://www.mojohaus.org/exec-maven-plugin/usage.html) or the [spring-boot-maven-plugin](https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/) -By running your application with maven, maven's system properties will be shared with your application. - -### In case you are running locally -If you want to debug your spring application locally, there are two ways to do this: -1. In IntelliJ, you can run and debug maven goals, see [here](https://www.jetbrains.com/help/idea/run-debug-configuration-maven.html). -The catch is not to let spring boot plugin to fork its JVM from the initial maven process. see [here](https://youtrack.jetbrains.com/issue/IDEA-175246) - -```xml - - org.springframework.boot - spring-boot-maven-plugin - - false - - -``` - -2. Or you can set a remote debugger on any IDE by adding spring boot JVM args for the debugger connexion. - -```xml - - org.springframework.boot - spring-boot-maven-plugin - - -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8282 - - -``` - -### In case you have a Non-Spring Java application -Spring boot manages system properties and environment variables differently that the standard way. -You will find here a [blog post](https://homeofthewizard.github.io/environmentvariables-in-java) that explains how the two works in java, and [here](https://www.baeldung.com/spring-boot-properties-env-variables) a post explaining spring's difference. -But in short, Spring allows you to access both via the same API (using placeholders like `${myVar}`). So for a spring application that requires environment variables, we can emulate them by injecting System properties instead. - -For a non Spring application that requires environment variables, we cannot do that, we need to give the JVM real environment variables. -Let's say you have a simple java application, with a main class like this -```java -package org.example; - -public class Main { - public static void main(String[] args) { - System.out.println("Env variable fetched: "+ System.getenv("ENV_VAR_SECRET")); - } -} -``` - -you have the following pom.xml -```xml - - - org.apache.maven.plugins - maven-jar-plugin - - - - true - org.example.Main - - - - - -``` - -This app is using some environment variables given by its execution environment -when run via `java -jar myApp.jar`. - -Now if we use the Vault Maven Plugin, we can fetch the secret, run the app via maven, and the env variable will be injected directly. -By adding the followings to your pom.xml like so: -```xml - - - org.apache.maven.plugins - maven-jar-plugin - - - - true - org.example.Main - - - - - - com.homeofthewizard - vault-maven-plugin - 1.1.2-SNAPSHOT - - - pull - initialize - - pull - - - - - - - https://your-vault-host - XXXXX - - - secret/data/myJavaApp - - - secretKey - mavenPropertyForSecret - - - - - - - - - - org.codehaus.mojo - exec-maven-plugin - 1.2.1 - - java - - ${mavenPropertyForSecret} - - - -classpath - - org.example.Main - - - - -``` -And VoilĂ ! :tada: -Now you just need to run `mvn vault:pull exec:exec` -The only thing to provide is the token to authenticate to Vault server (`XXXXX`). -Isn't that magical ? :mage_man: - -# 3. Application that does not use Maven -Many developers use .env files to manage environment variables on their localhost. -It may be the case even on production for application that do not have tools to fetch the secrets directly from a secure shared place like Vault. - -If that is the case, the plugin can generate a .env file for you. -The configuration `EnvFile` allows this. - -Then you can inject it with [dotenv-java](https://github.com/cdimascio/dotenv-java) for example. - -There are also IntelliJ plugins that help you inject secrets via .env files before running/debugging the application in your local environment. diff --git a/docs/index.markdown b/docs/index.markdown index 9c2a4f2..9398ced 100644 --- a/docs/index.markdown +++ b/docs/index.markdown @@ -28,7 +28,7 @@ By providing a simple configuration via a `pom.xml`, giving the necessary identi As a result your application can use its secrets directly from those env variables, which are existing only during it's execution, instead of storing them locally. Which is more secure :policeman: The only thing to manage is the credentials to login into Vault, and the list of secret keys you need :massage_man:. - +* * * ## Why and When to use it ? Today, most of the software applications are communicating with other applications, using some sort of credentials for identifying themselves before establishing a secure communication, or some encryption keys for securing the communication itself. @@ -67,8 +67,8 @@ But it may also be the case that : where lots of enterprises still provide Windows PCs without a Docker or K8s. This plugin aims to help all those cases :santa:. - +* * * ## Example use cases Some CI tools that already working with maven, for example liquibase or sonar-scanner which both have maven plugins, are the best suite for using the Vault Maven Plugin. @@ -80,4 +80,4 @@ Even if you do not use maven at all, you can create a `pom.xml` just for the con and pass the secrets to your application via some other way. Example: another maven plugin that exports the environment variables to the execution context of your application. -We provide an example for each use cases above, [further](/vault-maven-plugin/examples.html) in this documentation. \ No newline at end of file +We provide an example for each use cases above, [further](/vault-maven-plugin/examples.html) in this documentation. diff --git a/docs/usage.markdown b/docs/usage.markdown index 5dcb32b..892a416 100644 --- a/docs/usage.markdown +++ b/docs/usage.markdown @@ -21,8 +21,8 @@ To include the vault-maven-plugin in your project and use it, add the following This plugin supports pull and pushing Maven project properties from secrets stored in [HashiCorp](https://www.hashicorp.com) [Vault](https://www.vaultproject.io/). Inject them in maven's execution, or output as .env file, according to your need. You need to add the appropriate configuration to the plugin so that it will be executed as you want. -Follow the below explanations for the different configurations. - +Follow the below explanations for the different configurations. +* * * ## Pulling Secrets In order to pull secrets you must add an execution to the plugin. The following execution will pull secrets from `secret/user` path on the server `https://vault.example.com`. @@ -77,7 +77,7 @@ Note that the execution will fail if a specified secret key does not exist and t Also note that there is `` configuration tag that is not used here, that defines how to use the fetched credentials. By default, if you do not define this tag, the secrets will be injected as Maven properties. See below on the dedicated part of this configuration for detailed info. - +* * * ## Pushing Secrets In order to pull secrets you must add an execution to the plugin. The following execution will pull secrets from `secret/user` path on the server `https://vault.example.com`. @@ -127,7 +127,7 @@ In particular, this configuration will set the value of the `${project.password} ``` Note that the execution will fail if a specified project property does not exist and that an existing secret value will be overwritten. - +* * * ## Authentication In order to pull or push secrets you may have to authenticate to the vault, which is generally the case. Using a prefetched token works fine (provided in the `` tag), @@ -164,9 +164,9 @@ Using a prefetched token works fine (provided in the `` tag), ``` -But if you want to automate the login process as well you can do it via the plugin directly. -You can provide the configs under the `` tag. -:warning: You should now remove the `token` tag entirely. An empty token tag is not allowed. +But if you want to automate the login process as well, you can do it via the plugin directly. +You can provide the configs under the `` tag. +:warning: You should now remove the `token` tag entirely. An empty token tag is not allowed. You have the following options enabled currently (others will follow soon): * Github PAT @@ -215,6 +215,14 @@ Use `` under the `` tag, as in the following exampl For general information about github token authentication in hashicorp Vault, see [here](https://developer.hashicorp.com/vault/docs/auth/github). +{: .important } +If you have multiple environments using different auth methods, and you do not want to define them both in the pom.xml for keeping your config simple, +You can also omit this section and provide your config on the cli. + +for example: +```shell +mvn vaul:pull -D"vault.authenticationMethod=githubToken" -D"vault.github.pat=XXXXXXXXX" +``` ### How to use AppRole ? @@ -259,7 +267,17 @@ Use `` under the `` tag, as in the following example. For general information about AppRole authentication in hashicorp Vault, see [here](https://developer.hashicorp.com/vault/docs/auth/approle). +{: .important } +If you have multiple environments using different auth methods, and you do not want to define them both in the pom.xml for keeping your config simple, +You can also omit this section and provide your config on the cli. + +for example: +```shell +mvn vaul:pull -D"vault.authenticationMethod=appRole" -D"vault.appRole.roleId=XXXXXXXXX" -D"vault.appRole.secretId=XXXXXXXXX" +``` + +* * * ## How to use the fetched secrets There are 3 ways you can use the secrets once they are pulled from Vault server. By giving the corresponding value to the `` configuration: @@ -292,4 +310,13 @@ By giving the corresponding value to the `` configuration: -``` \ No newline at end of file +``` + +{: .important } +If you have multiple environments using different output methods, and you do not want to define them both in the pom.xml for keeping your config simple, +You can also omit this section and provide your config on the cli. + +for example: +```shell +mvn vaul:pull -D"vault.outputMethod=EnvFile" +``` diff --git a/docs/use-cases/appTypes.markdown b/docs/use-cases/appTypes.markdown index 40b2b8c..3fab620 100644 --- a/docs/use-cases/appTypes.markdown +++ b/docs/use-cases/appTypes.markdown @@ -100,6 +100,7 @@ then the liquibase plugin will run using the secret! No need to handle secrets in the execution environment anymore ! :massage_man: You only need to give set the execution environment's credentials to access Vault! +* * * # 2. Maven Applications {: .warning } @@ -276,6 +277,7 @@ Now you just need to run `mvn vault:pull exec:exec` The only thing to provide is the token to authenticate to Vault server (`XXXXX`). Isn't that magical ? :mage: +* * * # 3. Application that does not use Maven Many developers use .env files to manage environment variables on their localhost. It may be the case even on production for application that do not have tools to fetch the secrets directly from a secure shared place like Vault. diff --git a/docs/use-cases/executionEnvs.markdown b/docs/use-cases/executionEnvs.markdown index 2cb76b3..cb240ee 100644 --- a/docs/use-cases/executionEnvs.markdown +++ b/docs/use-cases/executionEnvs.markdown @@ -94,6 +94,7 @@ pipeline { } } ``` +* * * # 2. Production environments Let's say you have an application running on K8s, and you deploy your app and its secrets via a jenkins CD pipeline. @@ -134,6 +135,7 @@ you can use this plugin to run your application via maven, just like you do in l If you are interested in all the best practices for managing your secrets on PROD, see the blog post [here](https://homeofthewizard.github.io/secrets-in-java), +* * * # 3. Local development environments If you want to debug your spring application locally, there are two ways to do this: diff --git a/src/main/java/com/homeofthewizard/maven/plugins/vault/client/JOpenLibsVaultClient.java b/src/main/java/com/homeofthewizard/maven/plugins/vault/client/JOpenLibsVaultClient.java index 2112d3b..4801c5f 100644 --- a/src/main/java/com/homeofthewizard/maven/plugins/vault/client/JOpenLibsVaultClient.java +++ b/src/main/java/com/homeofthewizard/maven/plugins/vault/client/JOpenLibsVaultClient.java @@ -115,7 +115,7 @@ public void authenticateIfNecessary(List servers, } else if (!Objects.isNull(s.getAuthentication())) { factory.fromServer(s).login(); } else { - throw new VaultException("Either a Token of Authentication method must be provided !!\n" + throw new VaultException("Either a Token or Authentication method must be provided !!\n" + "Put in your server configuration in the pom.xml:\n" + "" + "YOUR_VAULT_TOKEN" diff --git a/src/main/java/com/homeofthewizard/maven/plugins/vault/config/authentication/AuthenticationMethodFactory.java b/src/main/java/com/homeofthewizard/maven/plugins/vault/config/authentication/AuthenticationMethodFactory.java index 33fa8d8..8494a05 100644 --- a/src/main/java/com/homeofthewizard/maven/plugins/vault/config/authentication/AuthenticationMethodFactory.java +++ b/src/main/java/com/homeofthewizard/maven/plugins/vault/config/authentication/AuthenticationMethodFactory.java @@ -36,7 +36,9 @@ public AuthenticationMethod fromServer(Server server) throws VaultException { /** * Factory method that helps to create the authentication config from arguments given from cli. + * @param server Server * @param systemProperties AuthenticationSystemProperties + * @param counter int * @return AuthenticationMethod subclass */ @Override diff --git a/src/test/java/com/homeofthewizard/maven/plugins/vault/client/TestVaultClients.java b/src/test/java/com/homeofthewizard/maven/plugins/vault/client/TestVaultClients.java index 0587566..84c1d53 100644 --- a/src/test/java/com/homeofthewizard/maven/plugins/vault/client/TestVaultClients.java +++ b/src/test/java/com/homeofthewizard/maven/plugins/vault/client/TestVaultClients.java @@ -33,7 +33,7 @@ public void testAuthenticationIfNecessaryWithMethod() throws VaultException { TreeMap map = new TreeMap<>(); map.put(githubTokenTag,"token"); Map vaultGithubToken = Map.of( - AuthenticationMethodFactory.APP_ROLE_TAG, map + AuthenticationMethodFactory.GITHUB_TOKEN_TAG, map ); var server = new Server("URL", null, false, null, vaultGithubToken, "NAMESPACE", List.of(), false, 1); var authenticationProviderMock = Mockito.mock(AuthenticationMethodFactory.class); @@ -46,7 +46,19 @@ public void testAuthenticationIfNecessaryWithMethod() throws VaultException { } @Test - public void testAuthenticationIfNecessaryWithoutMethod() { + public void testAuthenticationIfNecessaryWithCliArgs() throws VaultException { + var server = new Server("URL", null, false, null, null, "NAMESPACE", List.of(), false, 1); + var authenticationProviderMock = Mockito.mock(AuthenticationMethodFactory.class); + var authenticationMethodMock = Mockito.mock(AuthenticationMethod.class); + var vaultClient = VaultClient.create(); + when(authenticationProviderMock.fromSystemProperties(any(),any(),anyInt())).thenReturn(authenticationMethodMock); + doNothing().when(authenticationMethodMock).login(); + + vaultClient.authenticateIfNecessary(List.of(server), new AuthenticationSysProperties(List.of(AuthenticationMethodFactory.GITHUB_TOKEN_TAG), List.of(), List.of(), List.of()), authenticationProviderMock); + } + + @Test + public void testAuthenticationIfNecessaryWithoutMethodNorCliArgs() { var server = new Server("URL", null, false, null, null, "NAMESPACE", List.of(), false, 1); var vaultClient = VaultClient.create(); @@ -54,7 +66,7 @@ public void testAuthenticationIfNecessaryWithoutMethod() { VaultException.class, ()-> vaultClient.authenticateIfNecessary(List.of(server), new AuthenticationSysProperties(), null) ); - Assertions.assertTrue(ex.getMessage().contains("Either a Token of Authentication method must be provided !!")); + Assertions.assertTrue(ex.getMessage().contains("Either a Token or Authentication method must be provided !!")); } @Test diff --git a/src/test/java/com/homeofthewizard/maven/plugins/vault/config/authentication/TestGithubTokenAuthMethod.java b/src/test/java/com/homeofthewizard/maven/plugins/vault/config/authentication/TestGithubTokenAuthMethod.java index 03c17c5..0ca623a 100644 --- a/src/test/java/com/homeofthewizard/maven/plugins/vault/config/authentication/TestGithubTokenAuthMethod.java +++ b/src/test/java/com/homeofthewizard/maven/plugins/vault/config/authentication/TestGithubTokenAuthMethod.java @@ -14,6 +14,7 @@ import java.util.TreeMap; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class TestGithubTokenAuthMethod { @@ -37,4 +38,39 @@ public void testLogin() throws VaultException { Assert.assertTrue(server.getToken().equals("TOKEN")); } + + @Test + public void testLoginWithCliArgs() throws VaultException { + var server = new Server("URL",null,true,null,null,null,null,true,null); + var authMock = Mockito.mock(Auth.class); + var authResponseMock = Mockito.mock(AuthResponse.class); + when(authResponseMock.getAuthClientToken()).thenReturn("TOKEN"); + when(authMock.loginByGithub(any())).thenReturn(authResponseMock); + var githubMethod = new GithubTokenAuthMethod(authMock, "cliPat",server); + + githubMethod.login(); + + Assert.assertTrue(server.getToken().equals("TOKEN")); + } + + @Test + public void testLoginWithCliArgsInsteadPOMConfig() throws VaultException { + var githubTokenTag = GithubToken.class.getDeclaredFields()[0].getName(); + TreeMap map = new TreeMap<>(); + map.put(githubTokenTag,"token"); + Map vaultGithubToken = Map.of( + AuthenticationMethodFactory.GITHUB_TOKEN_TAG, map + ); + var server = new Server("URL",null,true,null,vaultGithubToken,null,null,true,null); + var authMock = Mockito.mock(Auth.class); + var authResponseMock = Mockito.mock(AuthResponse.class); + when(authResponseMock.getAuthClientToken()).thenReturn("TOKEN"); + when(authMock.loginByGithub(any())).thenReturn(authResponseMock); + var githubMethod = new GithubTokenAuthMethod(authMock, "cliToken",server); + + githubMethod.login(); + + verify(authMock).loginByGithub("cliToken"); + Assert.assertTrue(server.getToken().equals("TOKEN")); + } }