From 967dfd66d1f26d595418c4cc7f42638381d4d32e Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 14 Apr 2021 13:47:33 +0100 Subject: [PATCH] Update 'quarkus-oidc' and 'quarkus-oidc-client' to get secrets from CredentialsProvider --- .../main/asciidoc/credentials-provider.adoc | 2 + .../security-openid-connect-client.adoc | 74 ++++++++++-- ...ity-openid-connect-web-authentication.adoc | 109 ++++++++++++++++++ .../asciidoc/security-openid-connect.adoc | 13 +++ ...idcClientCredentialsJwtSecretTestCase.java | 3 +- .../client/OidcClientCredentialsTestCase.java | 3 +- .../quarkus/oidc/client/SecretProvider.java | 26 +++++ ...c-client-credentials-jwt-secret.properties | 2 +- ...ication-oidc-client-credentials.properties | 3 +- extensions/oidc-common/deployment/pom.xml | 4 + extensions/oidc-common/runtime/pom.xml | 4 + .../oidc/common/runtime/OidcCommonConfig.java | 82 ++++++++++++- .../oidc/common/runtime/OidcCommonUtils.java | 51 +++++--- .../oidc/test/CodeFlowDevModeTestCase.java | 3 +- .../io/quarkus/oidc/test/SecretProvider.java | 22 ++++ .../resources/application-dev-mode.properties | 3 +- 16 files changed, 373 insertions(+), 31 deletions(-) create mode 100644 extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/SecretProvider.java create mode 100644 extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/SecretProvider.java diff --git a/docs/src/main/asciidoc/credentials-provider.adoc b/docs/src/main/asciidoc/credentials-provider.adoc index ba46b2562b7ae..c2cc766280007 100644 --- a/docs/src/main/asciidoc/credentials-provider.adoc +++ b/docs/src/main/asciidoc/credentials-provider.adoc @@ -32,6 +32,8 @@ by the following credentials consumer extensions: * `reactive-db2-client` * `reactive-mysql-client` * `reactive-pg-client` +* `oidc` +* `oidc-client` All extensions that rely on username/password authentication also allow setting configuration properties in the `application.properties` as an alternative. But the `Credentials Provider` is the only option diff --git a/docs/src/main/asciidoc/security-openid-connect-client.adoc b/docs/src/main/asciidoc/security-openid-connect-client.adoc index e315a2e5da1b0..4af17b3374d8b 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client.adoc @@ -360,6 +360,28 @@ quarkus.oidc-client.client-id=quarkus-app quarkus.oidc-client.credentials.secret=mysecret ---- +or + +[source,properties] +---- +quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/ +quarkus.oidc-client.client-id=quarkus-app +quarkus.oidc-client.credentials.client-secret.value=mysecret +---- + +or with the secret retrieved from a link:credentials-provider[CredentialsProvider]: + +[source,properties] +---- +quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/ +quarkus.oidc-client.client-id=quarkus-app + +# This is a key which will be used to retrieve a secret from the map of credentails returned from CredentialsProvider +quarkus.oidc-client.credentials.client-secret.provider.key=mysecret-key +# Set it only if more than one CredentialsProvider can be registered +quarkus.oidc-client.credentials.client-secret.provider.name=oidc-credentials-provider +---- + `client_secret_post`: [source,properties] @@ -372,32 +394,56 @@ quarkus.oidc-client.credentials.client-secret.method=post `client_secret_jwt`: +[source,properties] +---- +quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus/ +quarkus.oidc.client-id=quarkus-app +quarkus.oidc.credentials.jwt.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow + +# This is a token key identifier 'kid' header - set it if your OpenId Connect provider requires it, +quarkus.oidc.credentials.jwt.token-key-id=mykey +---- + +or with the secret retrieved from a link:credentials-provider[CredentialsProvider]: + [source,properties] ---- quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/ quarkus.oidc-client.client-id=quarkus-app -quarkus.oidc-client.credentials.jwt.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow + +# This is a key which will be used to retrieve a secret from the map of credentails returned from CredentialsProvider +quarkus.oidc-client.credentials.jwt.secret-provider.key=mysecret-key +# Set it only if more than one CredentialsProvider can be registered +quarkus.oidc-client.credentials.jwt.secret-provider.name=oidc-credentials-provider ---- `private_key_jwt` with the PEM key file: [source,properties] ---- -quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/ -quarkus.oidc-client.client-id=quarkus-app -quarkus.oidc-client.credentials.jwt.key-file=privateKey.pem +quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus/ +quarkus.oidc.client-id=quarkus-app +quarkus.oidc.credentials.jwt.key-file=privateKey.pem + +# This is a token key identifier 'kid' header - set it if your OpenId Connect provider requires it +quarkus.oidc.credentials.jwt.token-key-id=mykey ---- `private_key_jwt` with the key store file: [source,properties] ---- -quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/ -quarkus.oidc-client.client-id=quarkus-app -quarkus.oidc-client.credentials.jwt.key-store-file=keystore.jks -quarkus.oidc-client.credentials.jwt.key-store-password=mypassword -quarkus.oidc-client.credentials.jwt.key-password=mykeypassword -quarkus.oidc-client.credentials.jwt.key-id=mykey +quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus/ +quarkus.oidc.client-id=quarkus-app +quarkus.oidc.credentials.jwt.key-store-file=keystore.jks +quarkus.oidc.credentials.jwt.key-store-password=mypassword +quarkus.oidc.credentials.jwt.key-password=mykeypassword +# Private key alias inside the keystore +quarkus.oidc.credentials.jwt.key-id=mykey + +# This is a token key identifier 'kid' header - set it if your OpenId Connect provider requires it, +# Note it can be different to the `quarkus.oidc.credentials.jwt.key-id` value +quarkus.oidc.credentials.jwt.token-key-id=mykey ---- Using `client_secret_jwt` or `private_key_jwt` authentication methods ensures that no client secret goes over the wire. @@ -525,6 +571,14 @@ quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientImpl".level=TRACE quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientImpl".min-level=TRACE ---- +Please enable `io.quarkus.oidc.client.runtime.OidcClientRecorder` `TRACE` level logging to see more details about the OidcClient initialization errors: + +[source, properties] +---- +quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientRecorder".level=TRACE +quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientRecorder".min-level=TRACE +---- + == Token endpoint configuration By default the token endpoint address is discovered by adding a `/.well-known/openid-configuration` path to the configured `quarkus.oidc-client.auth-server-url`. diff --git a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc index c2111c17280cc..b242e3be82ff3 100644 --- a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc @@ -581,6 +581,107 @@ It applies to ID tokens but also to access tokens in a JWT format if the `web-ap == Token Propagation Please see link:security-openid-connect-client#token-propagation[Token Propagation] section about the Authorization Code Flow access token propagation to the downstream services. +[[oidc-provider-client-authentication]] +=== Oidc Provider Client Authentication + +`quarkus.oidc.runtime.OidcProviderClient` is used when a remote request to an OpenId Connect Provider has to be done. It has to authenticate to the OpenId Connect Provider when the authorization code has to be exchanged for the ID, access and refresh tokens, when the ID and access tokens have to be refreshed or introspected. + +All the https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication[OIDC Client Authentication] options are supported, for example: + +`client_secret_basic`: + +[source,properties] +---- +quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus/ +quarkus.oidc.client-id=quarkus-app +quarkus.oidc.credentials.secret=mysecret +---- + +or + +[source,properties] +---- +quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus/ +quarkus.oidc.client-id=quarkus-app +quarkus.oidc.credentials.client-secret.value=mysecret +---- + +or with the secret retrieved from a link:credentials-provider[CredentialsProvider]: + +[source,properties] +---- +quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus/ +quarkus.oidc.client-id=quarkus-app + +# This is a key which will be used to retrieve a secret from the map of credentails returned from CredentialsProvider +quarkus.oidc.credentials.client-secret.provider.key=mysecret-key +# Set it only if more than one CredentialsProvider can be registered +quarkus.oidc.credentials.client-secret.provider.name=oidc-credentials-provider +---- + +`client_secret_post`: + +[source,properties] +---- +quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus/ +quarkus.oidc.client-id=quarkus-app +quarkus.oidc.credentials.client-secret.value=mysecret +quarkus.oidc.credentials.client-secret.method=post +---- + +`client_secret_jwt`: + +[source,properties] +---- +quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus/ +quarkus.oidc.client-id=quarkus-app +quarkus.oidc.credentials.jwt.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow +# This is a token key identifier 'kid' header - set it if your OpenId Connect provider requires it, +quarkus.oidc.credentials.jwt.token-key-id=mykey +---- + +or with the secret retrieved from a link:credentials-provider[CredentialsProvider]: + +[source,properties] +---- +quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus/ +quarkus.oidc.client-id=quarkus-app + +# This is a key which will be used to retrieve a secret from the map of credentails returned from CredentialsProvider +quarkus.oidc.credentials.jwt.secret-provider.key=mysecret-key +# Set it only if more than one CredentialsProvider can be registered +quarkus.oidc.credentials.jwt.secret-provider.name=oidc-credentials-provider +---- + +`private_key_jwt` with the PEM key file: + +[source,properties] +---- +quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus/ +quarkus.oidc.client-id=quarkus-app +quarkus.oidc.credentials.jwt.key-file=privateKey.pem +# This is a token key identifier 'kid' header - set it if your OpenId Connect provider requires it +quarkus.oidc.credentials.jwt.token-key-id=mykey +---- + +`private_key_jwt` with the key store file: + +[source,properties] +---- +quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus/ +quarkus.oidc.client-id=quarkus-app +quarkus.oidc.credentials.jwt.key-store-file=keystore.jks +quarkus.oidc.credentials.jwt.key-store-password=mypassword +quarkus.oidc.credentials.jwt.key-password=mykeypassword +# Private key alias inside the keystore +quarkus.oidc.credentials.jwt.key-id=mykey +# This is a token key identifier 'kid' header - set it if your OpenId Connect provider requires it, +# Note it can be different to the `quarkus.oidc.credentials.jwt.key-id` value +quarkus.oidc.credentials.jwt.token-key-id=mykey +---- + +Using `client_secret_jwt` or `private_key_jwt` authentication methods ensures that no client secret goes over the wire. + [[integration-testing]] == Testing @@ -751,6 +852,14 @@ quarkus.log.category."io.quarkus.oidc.runtime.OidcProvider".level=TRACE quarkus.log.category."io.quarkus.oidc.runtime.OidcProvider".min-level=TRACE ---- +Please enable `io.quarkus.oidc.runtime.OidcRecorder` `TRACE` level logging to see more details about the OidcProvider client initialization errors: + +[source, properties] +---- +quarkus.log.category."io.quarkus.oidc.runtime.OidcRecorder".level=TRACE +quarkus.log.category."io.quarkus.oidc.runtime.OidcRecorder".min-level=TRACE +---- + == Running behind a reverse proxy OIDC authentication mechanism can be affected if your Quarkus application is running behind a reverse proxy/gateway/firewall when HTTP `Host` header may be reset to the internal IP address, HTTPS connection may be terminated, etc. For example, an authorization code flow `redirect_uri` parameter may be set to the internal host instead of the expected external one. diff --git a/docs/src/main/asciidoc/security-openid-connect.adoc b/docs/src/main/asciidoc/security-openid-connect.adoc index 6b436794f3a4b..c2c754eb3d84b 100644 --- a/docs/src/main/asciidoc/security-openid-connect.adoc +++ b/docs/src/main/asciidoc/security-openid-connect.adoc @@ -507,6 +507,11 @@ Note it is also recommended to use `quarkus.oidc.token.audience` property to ver Please see link:security-openid-connect-client#token-propagation[Token Propagation] section about the Bearer access token propagation to the downstream services. +[[oidc-provider-authentication]] +=== Oidc Provider Client Authentication + +`quarkus.oidc.runtime.OidcProviderClient` is used when a remote request to an OpenId Connect Provider has to be done. If the bearer token has to be introspected then `OidcProviderClient` has to authenticate to the OpenId Connect Provider. Please see link:security-openid-connect-web-authentication#oidc-provider-client-authentication[OidcProviderClient Authentication] for more information about all the supported authentication options. + [[integration-testing]] == Testing @@ -867,6 +872,14 @@ quarkus.log.category."io.quarkus.oidc.runtime.OidcProvider".level=TRACE quarkus.log.category."io.quarkus.oidc.runtime.OidcProvider".min-level=TRACE ---- +Please enable `io.quarkus.oidc.runtime.OidcRecorder` `TRACE` level logging to see more details about the OidcProvider client initialization errors: + +[source, properties] +---- +quarkus.log.category."io.quarkus.oidc.runtime.OidcRecorder".level=TRACE +quarkus.log.category."io.quarkus.oidc.runtime.OidcRecorder".min-level=TRACE +---- + == External and Internal Access to OpenId Connect Provider Note that the OpenId Connect Provider externally accessible token and other endpoints may have different HTTP(S) URLs compared to the URLs auto-discovered or configured relative to `quarkus.oidc.auth-server-url` internal URL. For example, if your SPA acquires a token from an external token endpoint address and sends it to Quarkus as a Bearer token then an issuer verification failure may be reported by the endpoint. diff --git a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientCredentialsJwtSecretTestCase.java b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientCredentialsJwtSecretTestCase.java index a2fdfa558ad6a..97414f72ca219 100644 --- a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientCredentialsJwtSecretTestCase.java +++ b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientCredentialsJwtSecretTestCase.java @@ -18,7 +18,8 @@ public class OidcClientCredentialsJwtSecretTestCase { private static Class[] testClasses = { OidcClientsResource.class, - ProtectedResource.class + ProtectedResource.class, + SecretProvider.class }; @RegisterExtension diff --git a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientCredentialsTestCase.java b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientCredentialsTestCase.java index 4c071d98b7330..64d4d0426e5a1 100644 --- a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientCredentialsTestCase.java +++ b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientCredentialsTestCase.java @@ -18,7 +18,8 @@ public class OidcClientCredentialsTestCase { private static Class[] testClasses = { OidcClientsResource.class, - ProtectedResource.class + ProtectedResource.class, + SecretProvider.class }; @RegisterExtension diff --git a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/SecretProvider.java b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/SecretProvider.java new file mode 100644 index 0000000000000..59f1e6d03d784 --- /dev/null +++ b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/SecretProvider.java @@ -0,0 +1,26 @@ +package io.quarkus.oidc.client; + +import java.util.HashMap; +import java.util.Map; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Named; + +import io.quarkus.arc.Unremovable; +import io.quarkus.credentials.CredentialsProvider; + +@ApplicationScoped +@Unremovable +@Named("vault-secret-provider") +public class SecretProvider implements CredentialsProvider { + + @Override + public Map getCredentials(String credentialsProviderName) { + Map creds = new HashMap<>(); + creds.put("secret-from-vault", "secret"); + creds.put("secret-from-vault-for-jwt", + "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"); + return creds; + } + +} diff --git a/extensions/oidc-client/deployment/src/test/resources/application-oidc-client-credentials-jwt-secret.properties b/extensions/oidc-client/deployment/src/test/resources/application-oidc-client-credentials-jwt-secret.properties index 5ad7baebbc257..e957fce676428 100644 --- a/extensions/oidc-client/deployment/src/test/resources/application-oidc-client-credentials-jwt-secret.properties +++ b/extensions/oidc-client/deployment/src/test/resources/application-oidc-client-credentials-jwt-secret.properties @@ -3,4 +3,4 @@ quarkus.oidc.client-id=quarkus-app quarkus.oidc-client.client-enabled=false quarkus.oidc-client.jwt.auth-server-url=${quarkus.oidc.auth-server-url} quarkus.oidc-client.jwt.client-id=${quarkus.oidc.client-id} -quarkus.oidc-client.jwt.credentials.jwt.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow \ No newline at end of file +quarkus.oidc-client.jwt.credentials.jwt.secret-provider.key=secret-from-vault-for-jwt \ No newline at end of file diff --git a/extensions/oidc-client/deployment/src/test/resources/application-oidc-client-credentials.properties b/extensions/oidc-client/deployment/src/test/resources/application-oidc-client-credentials.properties index b91efb8b579c8..fa819fb5570e1 100644 --- a/extensions/oidc-client/deployment/src/test/resources/application-oidc-client-credentials.properties +++ b/extensions/oidc-client/deployment/src/test/resources/application-oidc-client-credentials.properties @@ -4,4 +4,5 @@ quarkus.oidc.credentials.secret=secret quarkus.oidc-client.auth-server-url=${quarkus.oidc.auth-server-url} quarkus.oidc-client.client-id=${quarkus.oidc.client-id} -quarkus.oidc-client.credentials.secret=${quarkus.oidc.credentials.secret} \ No newline at end of file +quarkus.oidc-client.credentials.client-secret.provider.name=vault-secret-provider +quarkus.oidc-client.credentials.client-secret.provider.key=secret-from-vault \ No newline at end of file diff --git a/extensions/oidc-common/deployment/pom.xml b/extensions/oidc-common/deployment/pom.xml index 0f3f3183de661..32303c947d577 100644 --- a/extensions/oidc-common/deployment/pom.xml +++ b/extensions/oidc-common/deployment/pom.xml @@ -30,6 +30,10 @@ io.quarkus quarkus-vertx-http-deployment + + io.quarkus + quarkus-credentials-deployment + io.quarkus quarkus-smallrye-jwt-build-deployment diff --git a/extensions/oidc-common/runtime/pom.xml b/extensions/oidc-common/runtime/pom.xml index 23d507919aa9d..49aab96541199 100644 --- a/extensions/oidc-common/runtime/pom.xml +++ b/extensions/oidc-common/runtime/pom.xml @@ -30,6 +30,10 @@ io.quarkus quarkus-vertx-http + + io.quarkus + quarkus-credentials + io.smallrye.reactive smallrye-mutiny-vertx-web-client diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java index 0a56c1763ea66..77891b7c75f9e 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java @@ -135,6 +135,14 @@ public void setClientSecret(Secret clientSecret) { this.clientSecret = clientSecret; } + public Jwt getJwt() { + return jwt; + } + + public void setJwt(Jwt jwt) { + this.jwt = jwt; + } + /** * Supports the client authentication methods which involve sending a client secret. * @@ -158,11 +166,17 @@ public static enum Method { } /** - * The client secret + * The client secret value - it will be ignored if 'secret.key' is set */ @ConfigItem public Optional value = Optional.empty(); + /** + * The Secret CredentialsProvider + */ + @ConfigItem + public Provider provider = new Provider(); + /** * Authentication method. */ @@ -184,6 +198,14 @@ public Optional getMethod() { public void setMethod(Method method) { this.method = Optional.of(method); } + + public Provider getSecretProvider() { + return provider; + } + + public void setSecretProvider(Provider secretProvider) { + this.provider = secretProvider; + } } /** @@ -202,6 +224,12 @@ public static class Jwt { @ConfigItem public Optional secret = Optional.empty(); + /** + * If provided, indicates that JWT is signed using a secret key provided by Secret CredentialsProvider + */ + @ConfigItem + public Provider secretProvider = new Provider(); + /** * If provided, indicates that JWT is signed using a private key in PEM or JWK format */ @@ -259,6 +287,58 @@ public int getLifespan() { public void setLifespan(int lifespan) { this.lifespan = lifespan; } + + public Optional getTokenKeyId() { + return tokenKeyId; + } + + public void setTokenKeyId(String tokenKeyId) { + this.tokenKeyId = Optional.of(tokenKeyId); + } + + public Provider getSecretProvider() { + return secretProvider; + } + + public void setSecretProvider(Provider secretProvider) { + this.secretProvider = secretProvider; + } + + } + + /** + * CredentialsProvider which provides a client secret + */ + @ConfigGroup + public static class Provider { + + /** + * The CredentialsProvider name which should only be set if more than one CredentialsProvider is registered + */ + @ConfigItem + public Optional name = Optional.empty(); + + /** + * The CredentialsProvider client secret key + */ + @ConfigItem + public Optional key = Optional.empty(); + + public Optional getName() { + return name; + } + + public void setName(String name) { + this.name = Optional.of(name); + } + + public Optional getKey() { + return key; + } + + public void setKey(String key) { + this.key = Optional.of(key); + } } } diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java index 86e166bd024d4..d4f60b10c466a 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java @@ -11,10 +11,14 @@ import java.util.Map; import java.util.Optional; import java.util.OptionalInt; +import java.util.function.Supplier; import javax.crypto.SecretKey; +import io.quarkus.credentials.CredentialsProvider; +import io.quarkus.credentials.runtime.CredentialsProviderFinder; import io.quarkus.oidc.common.runtime.OidcCommonConfig.Credentials; +import io.quarkus.oidc.common.runtime.OidcCommonConfig.Credentials.Provider; import io.quarkus.oidc.common.runtime.OidcCommonConfig.Credentials.Secret; import io.quarkus.oidc.common.runtime.OidcCommonConfig.Tls.Verification; import io.quarkus.runtime.TlsConfig; @@ -173,26 +177,46 @@ public static String formatConnectionErrorMessage(String authServerUrlString) { } public static boolean isClientSecretBasicAuthRequired(Credentials creds) { - return creds.secret.isPresent() || creds.clientSecret.value.isPresent() - && creds.clientSecret.method.orElseGet(() -> Secret.Method.BASIC) == Secret.Method.BASIC; + return creds.secret.isPresent() || + ((creds.clientSecret.value.isPresent() || creds.clientSecret.provider.key.isPresent()) + && creds.clientSecret.method.orElseGet(() -> Secret.Method.BASIC) == Secret.Method.BASIC); } public static boolean isClientJwtAuthRequired(Credentials creds) { - return creds.jwt.secret.isPresent() || creds.jwt.keyFile.isPresent() || creds.jwt.keyStoreFile.isPresent(); + return creds.jwt.secret.isPresent() || creds.jwt.secretProvider.key.isPresent() || creds.jwt.keyFile.isPresent() + || creds.jwt.keyStoreFile.isPresent(); } public static boolean isClientSecretPostAuthRequired(Credentials creds) { - return creds.clientSecret.value.isPresent() + return (creds.clientSecret.value.isPresent() || creds.clientSecret.provider.key.isPresent()) && creds.clientSecret.method.orElseGet(() -> Secret.Method.BASIC) == Secret.Method.POST; } public static String clientSecret(Credentials creds) { - return creds.secret.orElseGet(() -> creds.clientSecret.value.get()); + return creds.secret.orElse(creds.clientSecret.value.orElseGet(fromCredentialsProvider(creds.clientSecret.provider))); + } + + private static Supplier fromCredentialsProvider(Provider provider) { + return new Supplier() { + + @Override + public String get() { + if (provider.key.isPresent()) { + String providerName = provider.name.orElse(null); + CredentialsProvider credentialsProvider = CredentialsProviderFinder.find(providerName); + if (credentialsProvider != null) { + return credentialsProvider.getCredentials(providerName).get(provider.key.get()); + } + } + return null; + } + }; } public static Key clientJwtKey(Credentials creds) { - if (creds.jwt.secret.isPresent()) { - return KeyUtils.createSecretKeyFromSecret(creds.jwt.secret.get()); + if (creds.jwt.secret.isPresent() || creds.jwt.secretProvider.key.isPresent()) { + return KeyUtils + .createSecretKeyFromSecret(creds.jwt.secret.orElseGet(fromCredentialsProvider(creds.jwt.secretProvider))); } else { Key key = null; try { @@ -226,8 +250,8 @@ public static String signJwtWithKey(OidcCommonConfig oidcConfig, Key key) { .audience(getAuthServerUrl(oidcConfig)) .expiresIn(oidcConfig.credentials.jwt.lifespan) .jws(); - if (oidcConfig.credentials.jwt.tokenKeyId.isPresent()) { - builder.keyId(oidcConfig.credentials.jwt.tokenKeyId.get()); + if (oidcConfig.credentials.jwt.getTokenKeyId().isPresent()) { + builder.keyId(oidcConfig.credentials.jwt.getTokenKeyId().get()); } if (key instanceof SecretKey) { return builder.sign((SecretKey) key); @@ -248,19 +272,18 @@ public static void verifyConfigurationId(String defaultId, String configKey, Opt } public static String initClientSecretBasicAuth(OidcCommonConfig oidcConfig) { - if (OidcCommonUtils.isClientSecretBasicAuthRequired(oidcConfig.credentials)) { + if (isClientSecretBasicAuthRequired(oidcConfig.credentials)) { return OidcConstants.BASIC_SCHEME + " " + Base64.getEncoder().encodeToString( (oidcConfig.getClientId().get() + ":" - + OidcCommonUtils.clientSecret(oidcConfig.credentials)) - .getBytes(StandardCharsets.UTF_8)); + + clientSecret(oidcConfig.credentials)).getBytes(StandardCharsets.UTF_8)); } return null; } public static Key initClientJwtKey(OidcCommonConfig oidcConfig) { - if (OidcCommonUtils.isClientJwtAuthRequired(oidcConfig.credentials)) { - return OidcCommonUtils.clientJwtKey(oidcConfig.credentials); + if (isClientJwtAuthRequired(oidcConfig.credentials)) { + return clientJwtKey(oidcConfig.credentials); } return null; } diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java index f81ee0540ba3f..1d55d0df736ee 100644 --- a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java +++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java @@ -28,7 +28,8 @@ public class CodeFlowDevModeTestCase { ProtectedResource.class, UnprotectedResource.class, CustomTenantConfigResolver.class, - CustomTokenStateManager.class + CustomTokenStateManager.class, + SecretProvider.class }; @RegisterExtension diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/SecretProvider.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/SecretProvider.java new file mode 100644 index 0000000000000..d364b76f5aae3 --- /dev/null +++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/SecretProvider.java @@ -0,0 +1,22 @@ +package io.quarkus.oidc.test; + +import java.util.Collections; +import java.util.Map; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Named; + +import io.quarkus.arc.Unremovable; +import io.quarkus.credentials.CredentialsProvider; + +@ApplicationScoped +@Unremovable +@Named("vault-secret-provider") +public class SecretProvider implements CredentialsProvider { + + @Override + public Map getCredentials(String credentialsProviderName) { + return Collections.singletonMap("secret-from-vault", "secret"); + } + +} diff --git a/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties b/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties index 347397a43066b..15b1c51e72f52 100644 --- a/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties +++ b/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties @@ -2,7 +2,8 @@ quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus quarkus.oidc.tenant-enabled=false # This is a wrong client-id, will be updated to 'quarkus-web-app' in the dev mode test quarkus.oidc.client-id=client-dev -quarkus.oidc.credentials.secret=secret +quarkus.oidc.credentials.client-secret.provider.name=vault-secret-provider +quarkus.oidc.credentials.client-secret.provider.key=secret-from-vault quarkus.oidc.application-type=web-app quarkus.log.category."com.gargoylesoftware.htmlunit.javascript.host.css.CSSStyleSheet".level=FATAL