From 3e798b98c460fb0d6ec7d5229797e6cdd6091140 Mon Sep 17 00:00:00 2001 From: Thibault Vallin Date: Wed, 15 Nov 2023 09:07:38 +0100 Subject: [PATCH] 4.x: archetype fix MP security (#7947) Signed-off-by: tvallin --- .../common/files/application.abac.yaml | 34 - .../common/files/application.oidc.yaml | 15 - .../src/main/archetype/common/security.xml | 225 +----- .../main/archetype/mp/custom/custom-mp.xml | 1 + .../mp/custom/files/application.abac.yaml | 3 + .../custom}/files/application.google.yaml | 0 .../files/application.http-signature.yaml | 0 .../mp/custom/files/application.oidc.yaml | 16 + .../java/__pkg__/JwtApplication.java.mustache | 17 + .../java/__pkg__/JwtResource.java.mustache | 24 + .../java/__pkg__/OidcResource.java.mustache | 27 + .../archetype/mp/custom/security-outputs.xml | 132 ++++ .../main/archetype/se/common/common-se.xml | 7 + .../main/resources/application.yaml.mustache | 12 - .../main/archetype/se/custom/custom-se.xml | 3 +- .../custom}/files/application.jwt.yaml | 0 .../java/__pkg__/GoogleMain.java.mustache | 73 -- .../__pkg__/JwtOverrideService.java.mustache | 6 +- .../__pkg__/OutboundOverrideJwt.java.mustache | 100 --- .../java/__pkg__/SignatureMain.java.mustache | 221 ------ .../src/main/resources/WEB/google-login.html | 68 ++ .../files/src/main/resources/signing-jwk.json | 0 .../src/main/resources/verifying-jwk.json | 2 +- .../java/__pkg__/GoogleMainTest.java.mustache | 44 -- .../OutboundOverrideJwtTest.java.mustache | 18 +- .../__pkg__/SignatureMainTest.java.mustache | 8 +- .../archetype/se/custom/security-outputs.xml | 653 ++++++++++++++++++ .../src/main/archetype/se/custom/security.xml | 48 -- 28 files changed, 968 insertions(+), 789 deletions(-) delete mode 100644 archetypes/helidon/src/main/archetype/common/files/application.abac.yaml delete mode 100644 archetypes/helidon/src/main/archetype/common/files/application.oidc.yaml create mode 100644 archetypes/helidon/src/main/archetype/mp/custom/files/application.abac.yaml rename archetypes/helidon/src/main/archetype/{common => mp/custom}/files/application.google.yaml (100%) rename archetypes/helidon/src/main/archetype/{common => mp/custom}/files/application.http-signature.yaml (100%) create mode 100644 archetypes/helidon/src/main/archetype/mp/custom/files/application.oidc.yaml create mode 100644 archetypes/helidon/src/main/archetype/mp/custom/files/src/main/java/__pkg__/JwtApplication.java.mustache create mode 100644 archetypes/helidon/src/main/archetype/mp/custom/files/src/main/java/__pkg__/JwtResource.java.mustache create mode 100644 archetypes/helidon/src/main/archetype/mp/custom/files/src/main/java/__pkg__/OidcResource.java.mustache create mode 100644 archetypes/helidon/src/main/archetype/mp/custom/security-outputs.xml rename archetypes/helidon/src/main/archetype/{common => se/custom}/files/application.jwt.yaml (100%) delete mode 100644 archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/GoogleMain.java.mustache delete mode 100644 archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/OutboundOverrideJwt.java.mustache delete mode 100644 archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/SignatureMain.java.mustache create mode 100644 archetypes/helidon/src/main/archetype/se/custom/files/src/main/resources/WEB/google-login.html rename archetypes/helidon/src/main/archetype/{common => se/custom}/files/src/main/resources/signing-jwk.json (100%) rename archetypes/helidon/src/main/archetype/{common => se/custom}/files/src/main/resources/verifying-jwk.json (89%) delete mode 100644 archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/GoogleMainTest.java.mustache create mode 100644 archetypes/helidon/src/main/archetype/se/custom/security-outputs.xml delete mode 100644 archetypes/helidon/src/main/archetype/se/custom/security.xml diff --git a/archetypes/helidon/src/main/archetype/common/files/application.abac.yaml b/archetypes/helidon/src/main/archetype/common/files/application.abac.yaml deleted file mode 100644 index 432b05d3d9f..00000000000 --- a/archetypes/helidon/src/main/archetype/common/files/application.abac.yaml +++ /dev/null @@ -1,34 +0,0 @@ - - abac: - # prepares environment - # executes attribute validations - # validates that attributes were processed - # grants/denies access to resource - # - #### - # Combinations: - # # Will fail if any attribute is not validated and if any has failed validation - # fail-on-unvalidated: true - # fail-if-none-validated: true - # - # # Will fail if there is one or more attributes present and NONE of them is validated or if any has failed validation - # # Will NOT fail if there is at least one validated attribute and any number of not validated attributes (and NONE failed) - # fail-on-unvalidated: false - # fail-if-none-validated: true - # - # # Will fail if there is any attribute that failed validation - # # Will NOT fail if there are no failed validation or if there are NONE validated - # fail-on-unvalidated: false - # fail-if-none-validated: false - #### - # fail if an attribute was not validated (e.g. we do not know, whether it is valid or not) - # defaults to true - fail-on-unvalidated: true - # fail if none of the attributes were validated - # defaults to true - fail-if-none-validated: true -# policy-validator: -# validators: -# - class: "io.helidon.security.abac.policy.DefaultPolicyValidator" -# my-custom-policy-engine: -# some-key: "some value" -# another-key: "another value" diff --git a/archetypes/helidon/src/main/archetype/common/files/application.oidc.yaml b/archetypes/helidon/src/main/archetype/common/files/application.oidc.yaml deleted file mode 100644 index cfd8a1b9e3a..00000000000 --- a/archetypes/helidon/src/main/archetype/common/files/application.oidc.yaml +++ /dev/null @@ -1,15 +0,0 @@ - - oidc: - client-id: "client-id-of-this-service" - # See [EncryptionFilter](https://helidon.io/docs/latest/apidocs/io.helidon.config.encryption/io/helidon/config/encryption/EncryptionFilter.html) for details about encrypting passwords in configuration files. - client-secret: "client-secret-of-this-service" - identity-uri: "http://your-tenant.identity-server.com" - frontend-uri: "http://my-service:8080" - audience: "http://my-service" - cors: - allow-origins: ["http://foo.com", "http://there.com"] - allow-methods: ["PUT", "DELETE"] - outbound: - - name: "internal-services" - hosts: ["*.example.org"] - outbound-token: - header: "X-Internal-Auth" diff --git a/archetypes/helidon/src/main/archetype/common/security.xml b/archetypes/helidon/src/main/archetype/common/security.xml index 301d3692f67..6d5ccf3dfa3 100644 --- a/archetypes/helidon/src/main/archetype/common/security.xml +++ b/archetypes/helidon/src/main/archetype/common/security.xml @@ -29,144 +29,11 @@ optional="true"> - + diff --git a/archetypes/helidon/src/main/archetype/se/common/files/src/main/resources/application.yaml.mustache b/archetypes/helidon/src/main/archetype/se/common/files/src/main/resources/application.yaml.mustache index 0957bafc6cb..f59a08c265c 100644 --- a/archetypes/helidon/src/main/archetype/se/common/files/src/main/resources/application.yaml.mustache +++ b/archetypes/helidon/src/main/archetype/se/common/files/src/main/resources/application.yaml.mustache @@ -11,16 +11,4 @@ server: {{.}} {{/application-yaml-entries}} -{{#security}} -security: - config.require-encryption: false - properties: -{{#security-properties}} -{{.}} -{{/security-properties}} - providers: -{{#providers-config-entries}} -{{.}} -{{/providers-config-entries}} -{{/security}} diff --git a/archetypes/helidon/src/main/archetype/se/custom/custom-se.xml b/archetypes/helidon/src/main/archetype/se/custom/custom-se.xml index 7d3105be343..988a0cc38c2 100644 --- a/archetypes/helidon/src/main/archetype/se/custom/custom-se.xml +++ b/archetypes/helidon/src/main/archetype/se/custom/custom-se.xml @@ -25,11 +25,12 @@ - + + diff --git a/archetypes/helidon/src/main/archetype/common/files/application.jwt.yaml b/archetypes/helidon/src/main/archetype/se/custom/files/application.jwt.yaml similarity index 100% rename from archetypes/helidon/src/main/archetype/common/files/application.jwt.yaml rename to archetypes/helidon/src/main/archetype/se/custom/files/application.jwt.yaml diff --git a/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/GoogleMain.java.mustache b/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/GoogleMain.java.mustache deleted file mode 100644 index cafa5efce59..00000000000 --- a/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/GoogleMain.java.mustache +++ /dev/null @@ -1,73 +0,0 @@ -package {{package}}; - -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -import io.helidon.http.HttpMediaTypes; -import io.helidon.logging.common.LogConfig; -import io.helidon.webserver.WebServer; -import io.helidon.webserver.WebServerConfig; -import io.helidon.webserver.context.ContextFeature; -import io.helidon.webserver.staticcontent.StaticContentService; -import io.helidon.security.Security; -import io.helidon.security.SecurityContext; -import io.helidon.security.Subject; -import io.helidon.webserver.security.SecurityFeature; -import io.helidon.security.providers.google.login.GoogleTokenProvider; - -/** - * Google login button example main class using builders. - */ -@SuppressWarnings({"SpellCheckingInspection", "DuplicatedCode"}) -public final class GoogleMain { - - private GoogleMain() { - } - - /** - * Start the example. - * - * @param args ignored - */ - public static void main(String[] args) { - LogConfig.configureRuntime(); - WebServerConfig.Builder builder = WebServerConfig.builder(); - setup(builder); - WebServer server = builder.build(); - - long t = System.nanoTime(); - server.start(); - long time = System.nanoTime() - t; - - System.out.printf(""" - Server started in %d ms - Started server on localhost: %2$d - You can access this example at http://localhost:%2$d/index.html - - Check application.yaml in case you are behind a proxy to configure it - """, - TimeUnit.MILLISECONDS.convert(time, TimeUnit.NANOSECONDS), - server.port()); - } - - static void setup(WebServerConfig.Builder server) { - Security security = Security.builder() - .addProvider(GoogleTokenProvider.builder() - .clientId("your-client-id.apps.googleusercontent.com")) - .build(); - server.routing(routing -> routing - .addFeature(ContextFeature.create()) - .addFeature(SecurityFeature.create(security)) - .get("/rest/profile", SecurityFeature.authenticate(), - (req, res) -> { - Optional securityContext = req.context().get(SecurityContext.class); - res.headers().contentType(HttpMediaTypes.PLAINTEXT_UTF_8); - res.send("Response from builder based service, you are: \n" + securityContext - .flatMap(SecurityContext::user) - .map(Subject::toString) - .orElse("Security context is null")); - res.next(); - }) - .register(StaticContentService.create("/WEB"))); - } -} diff --git a/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/JwtOverrideService.java.mustache b/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/JwtOverrideService.java.mustache index cccdea39d02..5297e9d6de2 100644 --- a/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/JwtOverrideService.java.mustache +++ b/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/JwtOverrideService.java.mustache @@ -1,5 +1,7 @@ package {{package}}; +import io.helidon.security.EndpointConfig; +import io.helidon.security.SecurityContext; import io.helidon.webclient.http1.Http1Client; import io.helidon.webclient.security.WebClientSecurity; import io.helidon.webserver.WebServer; @@ -7,8 +9,6 @@ import io.helidon.webserver.http.HttpRules; import io.helidon.webserver.http.HttpService; import io.helidon.webserver.http.ServerRequest; import io.helidon.webserver.http.ServerResponse; -import io.helidon.security.SecurityContext; -import io.helidon.security.providers.jwt.JwtProvider; final class JwtOverrideService implements HttpService { @@ -32,7 +32,7 @@ final class JwtOverrideService implements HttpService { .orElseThrow(() -> new RuntimeException("WebServer not found in context")); String result = client.get("http://localhost:" + server.port("backend") + "/hello") - .property(JwtProvider.EP_PROPERTY_OUTBOUND_USER, "jill") + .property(EndpointConfig.PROPERTY_OUTBOUND_ID, "jill") .requestEntity(String.class); res.send("You are: " + context.userName() + ", backend service returned: " + result); diff --git a/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/OutboundOverrideJwt.java.mustache b/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/OutboundOverrideJwt.java.mustache deleted file mode 100644 index 1dc258ca079..00000000000 --- a/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/OutboundOverrideJwt.java.mustache +++ /dev/null @@ -1,100 +0,0 @@ -package {{package}}; - -import java.util.concurrent.TimeUnit; - -import io.helidon.http.Header; -import io.helidon.config.Config; -import io.helidon.config.ConfigSources; -import io.helidon.webserver.WebServer; -import io.helidon.webserver.WebServerConfig; -import io.helidon.webserver.context.ContextFeature; -import io.helidon.security.Principal; -import io.helidon.security.SecurityContext; -import io.helidon.security.Subject; -import io.helidon.webserver.security.SecurityFeature; - -/** - * Creates two services. - * First service invokes the second with outbound security. - * There are two endpoints: - *
    - *
  • One that does simple identity propagation and one that uses an explicit username
  • - *
  • One that uses basic authentication to authenticate users and JWT to propagate identity
  • - *
- one that does simple identity propagation and one that uses an explicit username. - *

- * The difference between this example and basic authentication example: - *

    - *
  • Configuration files (this example uses ones with -jwt.yaml suffix)
  • - *
  • Client property used to override username
  • - *
- */ -public final class OutboundOverrideJwt { - - private OutboundOverrideJwt() { - } - - /** - * Example that propagates identity and on one endpoint explicitly sets the username and password. - * - * @param args ignored - */ - public static void main(String[] args) { - WebServerConfig.Builder builder = WebServer.builder(); - setup(builder); - WebServer server = builder.build(); - - long t = System.nanoTime(); - server.start(); - long time = System.nanoTime() - t; - - server.context().register(server); - - System.out.printf(""" - Server started in %3$d ms - - *********************** - ** Endpoints: ** - *********************** - - http://localhost:%1$d/propagate - http://localhost:%1$d/override - - Backend service started on: http://localhost:%2$d/hello - - """, - server.port(), - server.port("backend"), - TimeUnit.MILLISECONDS.convert(time, TimeUnit.NANOSECONDS)); - } - - static void setup(WebServerConfig.Builder server) { - Config clientConfig = Config.create(ConfigSources.classpath("client-service-jwt.yaml")); - Config backendConfig = Config.create(ConfigSources.classpath("backend-service-jwt.yaml")); - - server.routing(routing -> routing - .addFeature(ContextFeature.create()) - .addFeature(SecurityFeature.create(clientConfig.get("security"))) - .register(new JwtOverrideService())) - - // backend that prints the current user - .putSocket("backend", socket -> socket - .routing(routing -> routing - .addFeature(ContextFeature.create()) - .addFeature(SecurityFeature.create(backendConfig.get("security"))) - .get("/hello", (req, res) -> { - - // This is the token. It should be bearer - req.headers().first(Header.AUTHORIZATION) - .ifPresent(System.out::println); - - String username = req.context() - .get(SecurityContext.class) - .flatMap(SecurityContext::user) - .map(Subject::principal) - .map(Principal::getName) - .orElse("Anonymous"); - - res.send(username); - }))); - } -} diff --git a/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/SignatureMain.java.mustache b/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/SignatureMain.java.mustache deleted file mode 100644 index 76a4614d00f..00000000000 --- a/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/SignatureMain.java.mustache +++ /dev/null @@ -1,221 +0,0 @@ -package {{package}}; - -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -import io.helidon.common.configurable.Resource; -import io.helidon.common.pki.Keys; -import io.helidon.webserver.WebServer; -import io.helidon.webserver.WebServerConfig; -import io.helidon.webserver.context.ContextFeature; -import io.helidon.webserver.http.HttpRouting; -import io.helidon.security.CompositeProviderFlag; -import io.helidon.security.CompositeProviderSelectionPolicy; -import io.helidon.security.Security; -import io.helidon.webserver.security.SecurityFeature; -import io.helidon.security.providers.common.OutboundConfig; -import io.helidon.security.providers.common.OutboundTarget; -import io.helidon.security.providers.httpauth.HttpBasicAuthProvider; -import io.helidon.security.providers.httpauth.SecureUserStore; -import io.helidon.security.providers.httpsign.HttpSignProvider; -import io.helidon.security.providers.httpsign.InboundClientDefinition; -import io.helidon.security.providers.httpsign.OutboundTargetDefinition; - -/** - * Example of authentication of service with http signatures, using configuration file as much as possible. - */ -@SuppressWarnings("DuplicatedCode") -public class SignatureMain { - - private static final Map USERS = new HashMap<>(); - - static { - addUser("jack", "password", List.of("user", "admin")); - addUser("jill", "password", List.of("user")); - addUser("john", "password", List.of()); - } - - private SignatureMain() { - } - - private static void addUser(String user, String password, List roles) { - USERS.put(user, new SecureUserStore.User() { - @Override - public String login() { - return user; - } - - char[] password() { - return password.toCharArray(); - } - - @Override - public boolean isPasswordValid(char[] password) { - return Arrays.equals(password(), password); - } - - @Override - public Collection roles() { - return roles; - } - }); - } - - /** - * Starts this example. - * - * @param args ignored - */ - public static void main(String[] args) { - WebServerConfig.Builder builder = WebServer.builder(); - setup(builder); - WebServer server = builder.build(); - server.context().register(server); - - long t = System.nanoTime(); - server.start(); - long time = System.nanoTime() - t; - - System.out.printf(""" - Server started in %1d ms - - Signature example: from builder - - Users: - jack/password in roles: user, admin - jill/password in roles: user - john/password in no roles - - *********************** - ** Endpoints: ** - *********************** - - Basic authentication, user role required, will use symmetric signatures for outbound: - http://localhost:%2$d/service1 - Basic authentication, user role required, will use asymmetric signatures for outbound: - http://localhost:%3$d/service1-rsa - - """, TimeUnit.MILLISECONDS.convert(time, TimeUnit.NANOSECONDS), server.port(), server.port("service2")); - } - - static void setup(WebServerConfig.Builder server) { - server.routing(SignatureMain::routing1) - .putSocket("service2", socket -> socket - .routing(SignatureMain::routing2)); - } - - private static void routing2(HttpRouting.Builder routing) { - SecurityFeature security = SecurityFeature.create(security2()) - .securityDefaults(SecurityFeature.authenticate()); - - routing.addFeature(ContextFeature.create()) - .addFeature(security) - .get("/service2*", SecurityFeature.rolesAllowed("user")) - .register(new Service2()); - } - - private static void routing1(HttpRouting.Builder routing) { - SecurityFeature security = SecurityFeature.create(security1()) - .securityDefaults(SecurityFeature.authenticate()); - routing.addFeature(ContextFeature.create()) - .addFeature(security) - .get("/service1*", SecurityFeature.rolesAllowed("user")) - .register(new Service1()); - } - - private static Security security2() { - return Security.builder() - .providerSelectionPolicy(CompositeProviderSelectionPolicy - .builder() - .addAuthenticationProvider("http-signatures", CompositeProviderFlag.OPTIONAL) - .addAuthenticationProvider("basic-auth") - .build()) - .addProvider(HttpBasicAuthProvider - .builder() - .realm("mic") - .userStore(users()), - "basic-auth") - .addProvider(HttpSignProvider.builder() - .addInbound(InboundClientDefinition - .builder("service1-hmac") - .principalName("Service1 - HMAC signature") - .hmacSecret("somePasswordForHmacShouldBeEncrypted") - .build()) - .addInbound(InboundClientDefinition - .builder("service1-rsa") - .principalName("Service1 - RSA signature") - .publicKeyConfig(Keys.builder() - .keystore(k -> k - .keystore(Resource.create("keystore.p12")) - .passphrase("password") - .certAlias("service_cert") - .build()) - .build()) - .build()), - "http-signatures") - .build(); - } - - private static Security security1() { - return Security.builder() - .providerSelectionPolicy(CompositeProviderSelectionPolicy - .builder() - .addOutboundProvider("basic-auth") - .addOutboundProvider("http-signatures") - .build()) - .addProvider(HttpBasicAuthProvider - .builder() - .realm("mic") - .userStore(users()) - .addOutboundTarget(OutboundTarget.builder("propagate-all").build()), - "basic-auth") - .addProvider(HttpSignProvider - .builder() - .outbound(OutboundConfig - .builder() - .addTarget(hmacTarget()) - .addTarget(rsaTarget()) - .build()), - "http-signatures") - .build(); - } - - private static OutboundTarget rsaTarget() { - return OutboundTarget.builder("service2-rsa") - .addHost("localhost") - .addPath("/service2-rsa.*") - .customObject(OutboundTargetDefinition.class, - OutboundTargetDefinition.builder("service1-rsa") - .privateKeyConfig(Keys.builder() - .keystore(k -> k - .keystore(Resource.create("keystore.p12")) - .passphrase("password") - .keyAlias("myPrivateKey") - .build()) - .build()) - .build()) - .build(); - } - - private static OutboundTarget hmacTarget() { - return OutboundTarget.builder("service2") - .addHost("localhost") - .addPath("/service2") - .customObject( - OutboundTargetDefinition.class, - OutboundTargetDefinition - .builder("service1-hmac") - .hmacSecret("somePasswordForHmacShouldBeEncrypted") - .build()) - .build(); - } - - private static SecureUserStore users() { - return login -> Optional.ofNullable(USERS.get(login)); - } -} diff --git a/archetypes/helidon/src/main/archetype/se/custom/files/src/main/resources/WEB/google-login.html b/archetypes/helidon/src/main/archetype/se/custom/files/src/main/resources/WEB/google-login.html new file mode 100644 index 00000000000..92ddcd61e89 --- /dev/null +++ b/archetypes/helidon/src/main/archetype/se/custom/files/src/main/resources/WEB/google-login.html @@ -0,0 +1,68 @@ + + + + Google Login Provider + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +

Login to Google.

+
+
+
+ + +
+
+
+ + + diff --git a/archetypes/helidon/src/main/archetype/common/files/src/main/resources/signing-jwk.json b/archetypes/helidon/src/main/archetype/se/custom/files/src/main/resources/signing-jwk.json similarity index 100% rename from archetypes/helidon/src/main/archetype/common/files/src/main/resources/signing-jwk.json rename to archetypes/helidon/src/main/archetype/se/custom/files/src/main/resources/signing-jwk.json diff --git a/archetypes/helidon/src/main/archetype/common/files/src/main/resources/verifying-jwk.json b/archetypes/helidon/src/main/archetype/se/custom/files/src/main/resources/verifying-jwk.json similarity index 89% rename from archetypes/helidon/src/main/archetype/common/files/src/main/resources/verifying-jwk.json rename to archetypes/helidon/src/main/archetype/se/custom/files/src/main/resources/verifying-jwk.json index 09df45ed5b0..5bfc903024a 100644 --- a/archetypes/helidon/src/main/archetype/common/files/src/main/resources/verifying-jwk.json +++ b/archetypes/helidon/src/main/archetype/se/custom/files/src/main/resources/verifying-jwk.json @@ -2,7 +2,7 @@ "keys": [ { "kty": "oct", - "kid": "example", + "kid": "helidon", "alg": "HS256", "key_ops": [ "sign", diff --git a/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/GoogleMainTest.java.mustache b/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/GoogleMainTest.java.mustache deleted file mode 100644 index 38071538e0f..00000000000 --- a/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/GoogleMainTest.java.mustache +++ /dev/null @@ -1,44 +0,0 @@ - -package {{package}}; - -import io.helidon.http.HeaderNames; -import io.helidon.http.Status; -import io.helidon.webserver.testing.junit5.ServerTest; -import io.helidon.webserver.testing.junit5.SetUpServer; -import io.helidon.webclient.http1.Http1Client; -import io.helidon.webclient.http1.Http1ClientResponse; -import io.helidon.webserver.WebServerConfig; - -import org.junit.jupiter.api.Test; - -import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -/** - * Unit test for {@link GoogleBuilderMain}. - */ -@ServerTest -public class GoogleMainTest { - - private final Http1Client client; - - GoogleMainTest(Http1Client client) { - this.client = client; - } - - @SetUpServer - public static void setup(WebServerConfig.Builder server) { - GoogleMain.setup(server); - } - - @Test - public void testEndpoint() { - try (Http1ClientResponse response = client.get("/rest/profile").request()) { - - assertThat(response.status(), is(Status.UNAUTHORIZED_401)); - assertThat(response.headers().first(HeaderNames.WWW_AUTHENTICATE), - optionalValue(is("Bearer realm=\"helidon\",scope=\"openid profile email\""))); - } - } -} diff --git a/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/OutboundOverrideJwtTest.java.mustache b/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/OutboundOverrideJwtTest.java.mustache index 72b95d1a19b..5742a2af785 100644 --- a/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/OutboundOverrideJwtTest.java.mustache +++ b/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/OutboundOverrideJwtTest.java.mustache @@ -2,16 +2,16 @@ package {{package}}; import java.net.URI; -import io.helidon.webserver.testing.junit5.ServerTest; -import io.helidon.webserver.testing.junit5.SetUpServer; +import io.helidon.security.EndpointConfig; +import io.helidon.security.Security; +import io.helidon.security.providers.httpauth.HttpBasicAuthProvider; import io.helidon.webclient.http1.Http1Client; -import io.helidon.webclient.http1.Http1ClientRequest; import io.helidon.webclient.http1.Http1ClientResponse; import io.helidon.webclient.security.WebClientSecurity; import io.helidon.webserver.WebServer; import io.helidon.webserver.WebServerConfig; -import io.helidon.security.Security; -import io.helidon.security.providers.httpauth.HttpBasicAuthProvider; +import io.helidon.webserver.testing.junit5.ServerTest; +import io.helidon.webserver.testing.junit5.SetUpServer; import org.junit.jupiter.api.Test; @@ -46,8 +46,8 @@ public class OutboundOverrideJwtTest { public void testOverrideExample() { try (Http1ClientResponse response = client.get() .path("/override") - .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, "jack") - .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, "password") + .property(EndpointConfig.PROPERTY_OUTBOUND_ID, "jack") + .property(EndpointConfig.PROPERTY_OUTBOUND_SECRET, "password") .request()) { assertThat(response.status().code(), is(200)); @@ -61,8 +61,8 @@ public class OutboundOverrideJwtTest { public void testPropagateExample() { try (Http1ClientResponse response = client.get() .path("/propagate") - .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER, "jack") - .property(HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD, "password") + .property(EndpointConfig.PROPERTY_OUTBOUND_ID, "jack") + .property(EndpointConfig.PROPERTY_OUTBOUND_SECRET, "password") .request()) { assertThat(response.status().code(), is(200)); diff --git a/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/SignatureMainTest.java.mustache b/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/SignatureMainTest.java.mustache index 2f03ecf9751..bdd4e56be1d 100644 --- a/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/SignatureMainTest.java.mustache +++ b/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/SignatureMainTest.java.mustache @@ -13,8 +13,8 @@ import io.helidon.security.providers.httpauth.HttpBasicAuthProvider; import org.junit.jupiter.api.Test; -import static io.helidon.security.providers.httpauth.HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_PASSWORD; -import static io.helidon.security.providers.httpauth.HttpBasicAuthProvider.EP_PROPERTY_OUTBOUND_USER; +import static io.helidon.security.EndpointConfig.PROPERTY_OUTBOUND_SECRET; +import static io.helidon.security.EndpointConfig.PROPERTY_OUTBOUND_ID; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.is; @@ -50,8 +50,8 @@ public abstract class SignatureMainTest { private void test(String uri, Set expectedRoles, Set invalidRoles, String service) { try (Http1ClientResponse response = client.get(uri) - .property(EP_PROPERTY_OUTBOUND_USER, "jack") - .property(EP_PROPERTY_OUTBOUND_PASSWORD, "password") + .property(PROPERTY_OUTBOUND_ID, "jack") + .property(PROPERTY_OUTBOUND_SECRET, "password") .request()) { assertThat(response.status().code(), is(200)); diff --git a/archetypes/helidon/src/main/archetype/se/custom/security-outputs.xml b/archetypes/helidon/src/main/archetype/se/custom/security-outputs.xml new file mode 100644 index 00000000000..ea13511f7da --- /dev/null +++ b/archetypes/helidon/src/main/archetype/se/custom/security-outputs.xml @@ -0,0 +1,653 @@ + + + + + + + + + + + + + + + + io.helidon.security.providers + helidon-security-providers-oidc + + + io.helidon.security.providers + helidon-security-providers-idcs-mapper + + + + java.util.Optional + + + io.helidon.common.context.Contexts + io.helidon.http.HttpMediaTypes + io.helidon.security.Security + io.helidon.security.SecurityContext + io.helidon.security.Subject + io.helidon.security.providers.oidc.OidcFeature + + + + + + + + + { + Optional securityContext = req.context().get(SecurityContext.class); + res.headers().contentType(HttpMediaTypes.PLAINTEXT_UTF_8); + res.send("Response from config based service, you are: \n" + securityContext + .flatMap(SecurityContext::user) + .map(Subject::toString) + .orElse("Security context is null")); + }) + .get("/loggedout", (req, res) -> res.send("You have been logged out"))]]> + + + io.helidon.security + io.helidon.security.providers.oidc + + + .identity.oraclecloud.com + 2. idcs-client-id : This is obtained from your IDCS application in the IDCS console + 3. idcs-client-secret : This is obtained from your IDCS application in the IDCS console + 4. frontend-uri : This is the base URL of your application when run, e.g. `http://localhost:7987` + 5. proxy-host : Your proxy server if needed + 6. scope-audience : This is the scope audience which MUST match the primary audience in the IDCS resource, recommendation is not to have a trailing slash (/) + +## Try the application + +Build and run the application and then try the endpoints: + +1. Open http://localhost:7987/rest/profile in your browser. This should present + you with a response highlighting your logged in role (null) correctly as you are not logged in +2. Open `http://localhost:7987/oidc/logout` in your browser. This will log you out from your IDCS and Helidon sessions +]]> + + + + + + + + files + + src/*/resources/**/static.js/google-app.js + + + + + + + io.helidon.bundles + helidon-bundles-config + + + io.helidon.webserver + helidon-webserver-static-content + + + + java.util.Optional + + + io.helidon.http.HttpMediaTypes + io.helidon.security.Security + io.helidon.security.SecurityContext + io.helidon.security.Subject + io.helidon.security.providers.google.login.GoogleTokenProvider + io.helidon.webserver.WebServer + io.helidon.webserver.WebServerConfig + io.helidon.webserver.context.ContextFeature + io.helidon.webserver.security.SecurityFeature + io.helidon.webserver.staticcontent.StaticContentService + + + + + + + + + { + Optional securityContext = req.context().get(SecurityContext.class); + res.headers().contentType(HttpMediaTypes.PLAINTEXT_UTF_8); + res.send("Response from builder based service, you are: \n" + securityContext + .flatMap(SecurityContext::user) + .map(Subject::toString) + .orElse("Security context is null")); + res.next(); + }) + .register(StaticContentService.create("/WEB")) +]]> + + + + + + + + + + + + + + + + + + + + files + + src/*/java/**/JwtOverrideService.java.mustache + + + + files + + src/*/resources/**/backend-service-jwt.yaml + src/*/resources/**/client-service-jwt.yaml + src/*/resources/**/signing-jwk.json + src/*/resources/**/verifying-jwk.json + + + + + + io.helidon.webclient + helidon-webclient-security + + + io.helidon.security.providers + helidon-security-providers-http-auth + + + io.helidon.security.providers + helidon-security-providers-jwt + + + io.helidon.bundles + helidon-bundles-config + + + + io.helidon.config.Config + io.helidon.config.ConfigSources + io.helidon.http.HeaderNames + io.helidon.security.Principal + io.helidon.security.SecurityContext + io.helidon.security.Subject + io.helidon.webserver.WebServer + io.helidon.webserver.WebServerConfig + io.helidon.webserver.security.SecurityHttpFeature + + + + + + + + + socket + .routing(routing -> routing + .addFeature(SecurityHttpFeature.create(backendConfig.get("security.web-server"))) + .get("/hello", (req, res) -> { + + // This is the token. It should be bearer + req.headers().first(HeaderNames.AUTHORIZATION) + .ifPresent(System.out::println); + + String username = req.context() + .get(SecurityContext.class) + .flatMap(SecurityContext::user) + .map(Subject::principal) + .map(Principal::getName) + .orElse("Anonymous"); + + res.send(username); + }))) +]]> + + + + + + + + + io.helidon.webclient.security + io.helidon.security.providers.jwt + io.helidon.webclient.http1 + + + + + + + + + + + files + + src/*/java/**/Service1.java.mustache + src/*/java/**/Service2.java.mustache + + + + + + io.helidon.webclient + helidon-webclient-security + + + io.helidon.bundles + helidon-bundles-security + + + io.helidon.config + helidon-config-hocon + + + + java.util.Arrays + java.util.Collection + java.util.HashMap + java.util.List + java.util.Map + java.util.Optional + + + io.helidon.common.configurable.Resource + io.helidon.common.pki.Keys + io.helidon.security.CompositeProviderFlag + io.helidon.security.CompositeProviderSelectionPolicy + io.helidon.security.Security + io.helidon.security.providers.common.OutboundConfig + io.helidon.security.providers.common.OutboundTarget + io.helidon.security.providers.httpauth.HttpBasicAuthProvider + io.helidon.security.providers.httpauth.SecureUserStore + io.helidon.security.providers.httpsign.HttpSignProvider + io.helidon.security.providers.httpsign.InboundClientDefinition + io.helidon.security.providers.httpsign.OutboundTargetDefinition + io.helidon.webserver.security.SecurityFeature + io.helidon.webserver.security.SecurityHttpFeature + + + USERS = new HashMap<>(); + + static { + addUser("jack", "password", List.of("user", "admin")); + addUser("jill", "password", List.of("user")); + addUser("john", "password", List.of()); + } + + private static void addUser(String user, String password, List roles) { + USERS.put(user, new SecureUserStore.User() { + @Override + public String login() { + return user; + } + + char[] password() { + return password.toCharArray(); + } + + @Override + public boolean isPasswordValid(char[] password) { + return Arrays.equals(password(), password); + } + + @Override + public Collection roles() { + return roles; + } + }); + } +]]> + + + + + + + + + k + .keystore(Resource.create("keystore.p12")) + .passphrase("password") + .certAlias("service_cert") + .build()) + .build()) + .build()), + "http-signatures") + .build(); + } + + private static Security security1() { + return Security.builder() + .providerSelectionPolicy(CompositeProviderSelectionPolicy + .builder() + .addOutboundProvider("basic-auth") + .addOutboundProvider("http-signatures") + .build()) + .addProvider(HttpBasicAuthProvider + .builder() + .realm("mic") + .userStore(users()) + .addOutboundTarget(OutboundTarget.builder("propagate-all").build()), + "basic-auth") + .addProvider(HttpSignProvider + .builder() + .outbound(OutboundConfig + .builder() + .addTarget(hmacTarget()) + .addTarget(rsaTarget()) + .build()), + "http-signatures") + .build(); + } + + private static OutboundTarget rsaTarget() { + return OutboundTarget.builder("service2-rsa") + .addHost("localhost") + .addPath("/service2-rsa.*") + .customObject(OutboundTargetDefinition.class, + OutboundTargetDefinition.builder("service1-rsa") + .privateKeyConfig(Keys.builder() + .keystore(k -> k + .keystore(Resource.create("keystore.p12")) + .passphrase("password") + .keyAlias("myPrivateKey") + .build()) + .build()) + .build()) + .build(); + } + + private static OutboundTarget hmacTarget() { + return OutboundTarget.builder("service2") + .addHost("localhost") + .addPath("/service2") + .customObject( + OutboundTargetDefinition.class, + OutboundTargetDefinition + .builder("service1-hmac") + .hmacSecret("somePasswordForHmacShouldBeEncrypted") + .build()) + .build(); + } + + private static SecureUserStore users() { + return login -> Optional.ofNullable(USERS.get(login)); + } +]]> + + + socket + .routing(Main::routing2))]]> + + + + + + io.helidon.webclient.http1 + io.helidon.common.pki + io.helidon.webserver.context + io.helidon.security.providers.common + io.helidon.security.providers.httpauth + io.helidon.security.providers.httpsign + + + + + + + + + + + + + io.helidon.security.abac + helidon-security-abac-scope + + + io.helidon.security.abac + helidon-security-abac-role + + + + + + + + + + + + + + + + + + + io.helidon.webserver + helidon-webserver-security + + + + + + + io.helidon.security.integration.common + io.helidon.webserver.security + + + + + + + diff --git a/archetypes/helidon/src/main/archetype/se/custom/security.xml b/archetypes/helidon/src/main/archetype/se/custom/security.xml deleted file mode 100644 index 7bccd44854a..00000000000 --- a/archetypes/helidon/src/main/archetype/se/custom/security.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - files - - src/*/java/**/OutboundOverrideJwtExample.java.mustache - src/*/java/**/OutboundOverrideJwtExampleTest.java.mustache - src/*/java/**/JwtOverrideService.java.mustache - src/*/java/**/GoogleMain.java.mustache - src/*/java/**/GoogleMainTest.java.mustache - src/*/java/**/Service1.java.mustache - src/*/java/**/Service2.java.mustache - src/*/java/**/SignatureMain.java.mustache - src/*/java/**/SignatureMainTest.java.mustache - /**/trick-to-avoid-empty-tag - - - - files - - src/*/resources/**/backend-service-jwt.yaml - src/*/resources/**/client-service-jwt.yaml - src/*/resources/**/google.js/google-app.js - /**/trick-to-avoid-empty-tag - - - -