diff --git a/.gitignore b/.gitignore index bdf57ce..16393aa 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ nb-configuration.xml # Local environment .env +.adhoc diff --git a/deployment/templates/deploymentconfig.yaml b/deployment/templates/deploymentconfig.yaml index 3eb09a0..8800489 100644 --- a/deployment/templates/deploymentconfig.yaml +++ b/deployment/templates/deploymentconfig.yaml @@ -28,6 +28,8 @@ spec: spec: containers: - envFrom: + - secretRef: + name: {{ .Values.name }}-secret readinessProbe: httpGet: path: /health/ready diff --git a/deployment/templates/route.yaml b/deployment/templates/route.yaml index 1b249e7..5a9dbc2 100644 --- a/deployment/templates/route.yaml +++ b/deployment/templates/route.yaml @@ -1,4 +1,3 @@ -{{- if .Values.development }} apiVersion: route.openshift.io/v1 kind: Route metadata: @@ -21,4 +20,3 @@ spec: wildcardPolicy: None status: ingress: [] -{{- end }} diff --git a/deployment/templates/secret.yaml b/deployment/templates/secret.yaml new file mode 100644 index 0000000..9280ba1 --- /dev/null +++ b/deployment/templates/secret.yaml @@ -0,0 +1,11 @@ +{{- if .Values.development }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.name }}-secret +type: Opaque +stringData: + JWT_PUBKICKEY_LOCATION: "{{ .Values.jwtPublicKeyLocation }}" + JWT_ISSUER: "{{ .Values.jwtIssuer }}" + JWT_ENABLE: "{{ .Values.jwtEnable }}" +{{- end }} diff --git a/deployment/values-dev.yaml b/deployment/values-dev.yaml index f1054cf..baaf71d 100644 --- a/deployment/values-dev.yaml +++ b/deployment/values-dev.yaml @@ -13,6 +13,10 @@ git: uri: https://github.com/rht-labs/lodestar-config.git ref: master +jwtPublicKeyLocation: false +jwtIssuer: false +jwtEnable: true + runtimeConfigs: - name: lodestar-runtime file: lodestar-runtime-config.yaml @@ -22,4 +26,4 @@ runtimeConfigs: path: /runtime - name: lodestar-runtime-two file: lodestar-runtime-config-two.yaml - path: /runtime \ No newline at end of file + path: /runtime diff --git a/pom.xml b/pom.xml index a6d6de1..ad27fa3 100644 --- a/pom.xml +++ b/pom.xml @@ -1,179 +1,185 @@ - - 4.0.0 - com.redhat.labs - lodestar-config - 1.0.0-SNAPSHOT - - 3.8.1 - true - 11 - 11 - UTF-8 - UTF-8 - 1.13.3.Final - quarkus-universe-bom - io.quarkus - 1.13.3.Final - 3.0.0-M5 - 1.18.20 - https://sonarcloud.io - rht-labs_lodestar-config - rht-labs - target/jacoco.exec - - - - - ${quarkus.platform.group-id} - ${quarkus.platform.artifact-id} - ${quarkus.platform.version} - pom - import - - - - - - - io.quarkus - quarkus-resteasy - - - io.quarkus - quarkus-resteasy-jsonb - - - - io.quarkus - quarkus-smallrye-openapi - - - - io.quarkus - quarkus-smallrye-health - - - - io.quarkus - quarkus-smallrye-metrics - - - - org.projectlombok - lombok - ${lombok.version} - provided - - - io.quarkus - quarkus-arc - - - io.quarkus - quarkus-junit5 - test - - - io.rest-assured - rest-assured - test - - - - - - io.quarkus - quarkus-maven-plugin - ${quarkus-plugin.version} - true - - - - build - generate-code - generate-code-tests - - - - - - maven-compiler-plugin - ${compiler-plugin.version} - - ${maven.compiler.parameters} - - - - maven-surefire-plugin - ${surefire-plugin.version} - - - org.jboss.logmanager.LogManager - ${maven.home} - - - - - org.jacoco - jacoco-maven-plugin - 0.8.7 - - - jacoco-initialize - - prepare-agent - - test-compile - - - jacoco-site - verify - - report - - - - - - - - - native - - - native - - - - - - maven-failsafe-plugin - ${surefire-plugin.version} - - - - integration-test - verify - - - - ${project.build.directory}/${project.build.finalName}-runner - org.jboss.logmanager.LogManager - ${maven.home} - - - - - - - - - native - - - + + 4.0.0 + com.redhat.labs + lodestar-config + 1.0.0-SNAPSHOT + + 3.8.1 + 1.18.20 + true + 11 + 11 + UTF-8 + UTF-8 + 1.13.3.Final + quarkus-universe-bom + io.quarkus + 1.13.3.Final + https://sonarcloud.io + target/jacoco.exec + rht-labs + rht-labs_lodestar-config + 3.0.0-M5 + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-resteasy-jsonb + + + io.quarkus + quarkus-smallrye-openapi + + + io.quarkus + quarkus-smallrye-health + + + io.quarkus + quarkus-smallrye-metrics + + + org.projectlombok + lombok + ${lombok.version} + provided + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-test-oidc-server + test + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + io.quarkus + quarkus-smallrye-jwt-build + + + io.quarkus + quarkus-smallrye-jwt + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus-plugin.version} + true + + + + build + generate-code + generate-code-tests + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + ${maven.compiler.parameters} + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + org.jacoco + jacoco-maven-plugin + 0.8.7 + + + jacoco-initialize + test-compile + + prepare-agent + + + + jacoco-site + verify + + report + + + + + + + + + native + + + native + + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + native + + + diff --git a/src/main/java/com/redhat/labs/lodestar/resource/RuntimeConfigResource.java b/src/main/java/com/redhat/labs/lodestar/resource/RuntimeConfigResource.java index e1f882c..fdac576 100644 --- a/src/main/java/com/redhat/labs/lodestar/resource/RuntimeConfigResource.java +++ b/src/main/java/com/redhat/labs/lodestar/resource/RuntimeConfigResource.java @@ -2,6 +2,7 @@ import java.util.Optional; +import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -10,17 +11,30 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.enums.SecuritySchemeType; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; +import org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement; +import org.eclipse.microprofile.openapi.annotations.security.SecurityScheme; + import com.redhat.labs.lodestar.service.RuntimeConfigService; +@RequestScoped @Path("/api/v1/configs/runtime") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) +@SecurityScheme(securitySchemeName = "jwt", type = SecuritySchemeType.HTTP, scheme = "bearer", bearerFormat = "JWT") public class RuntimeConfigResource { @Inject RuntimeConfigService configService; @GET + @SecurityRequirement(name = "jwt", scopes = {}) + @APIResponses(value = { @APIResponse(responseCode = "401", description = "Missing or Invalid JWT"), + @APIResponse(responseCode = "200", description = "Configuration file data has been returned.") }) + @Operation(summary = "Returns configuration file data.") public String get(@QueryParam("type") Optional type) { return configService.getRuntimeConfiguration(type); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e86b074..720bd74 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,5 +12,21 @@ quarkus.http.cors=true # Open API quarkus.swagger-ui.always-include=true +# jwt verification configuration +mp.jwt.verify.publickey.location=${JWT_PUBKICKEY_LOCATION:META-INF/resources/publicKey.pem} +mp.jwt.verify.issuer=${JWT_ISSUER:https://quarkus.io/using-jwt-rbac} + +# enable jwt support +quarkus.smallrye-jwt.enabled=${JWT_ENABLE:true} + +# define auth roles +quarkus.http.auth.policy.role-reader.roles-allowed=reader + +# set the /config endpoint(s) to reader or admin role +quarkus.http.auth.permission.read.paths=/api/* +quarkus.http.auth.permission.read.policy=role-reader + # Runtime Configuration Service configs.runtime.base.file=${RUNTIME_BASE_CONFIG_FILE:/runtime/lodestar-runtime-config.yaml} + +mp.openapi.extensions.smallrye.info.title=LodeStar Config API diff --git a/src/test/java/com/redhat/labs/lodestar/resource/RuntimeConfigResourceTest.java b/src/test/java/com/redhat/labs/lodestar/resource/RuntimeConfigResourceTest.java index 9a4d651..c4e2958 100644 --- a/src/test/java/com/redhat/labs/lodestar/resource/RuntimeConfigResourceTest.java +++ b/src/test/java/com/redhat/labs/lodestar/resource/RuntimeConfigResourceTest.java @@ -3,12 +3,19 @@ import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.is; +import java.util.Arrays; +import java.util.HashSet; + import org.junit.jupiter.api.Test; +import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.oidc.server.OidcWiremockTestResource; import io.restassured.http.ContentType; +import io.smallrye.jwt.build.Jwt; @QuarkusTest +@QuarkusTestResource(OidcWiremockTestResource.class) class RuntimeConfigResourceTest { static final String RUNTIME_CONFIG_API = "/api/v1/configs/runtime"; @@ -16,7 +23,7 @@ class RuntimeConfigResourceTest { @Test void testGetRuntimeConfigurationNoType() { - given().when().contentType(ContentType.JSON).get(RUNTIME_CONFIG_API).then().statusCode(200).body(is("\n{\n" + " \"basic_information\": {\n" + " \"engagement_types\": {\n" + given().auth().oauth2(getValidAccessToken()).when().contentType(ContentType.JSON).get(RUNTIME_CONFIG_API).then().statusCode(200).body(is("\n{\n" + " \"basic_information\": {\n" + " \"engagement_types\": {\n" + " \"options\": [\n" + " {\n" + " \"label\": \"Type One\",\n" + " \"value\": \"TypeOne\"\n" + " },\n" + " {\n" + " \"label\": \"TypeTwo\",\n" @@ -36,7 +43,7 @@ void testGetRuntimeConfigurationNoType() { @Test void testGetRuntimeConfigurationTypeOne() { - given().when().contentType(ContentType.JSON).queryParam("type", "TypeOne").get(RUNTIME_CONFIG_API).then().statusCode(200).body(is("\n" + "{\n" + " \"more_information\": {\n" + " \"another_value\": \"type one\",\n" + given().auth().oauth2(getValidAccessToken()).when().contentType(ContentType.JSON).queryParam("type", "TypeOne").get(RUNTIME_CONFIG_API).then().statusCode(200).body(is("\n" + "{\n" + " \"more_information\": {\n" + " \"another_value\": \"type one\",\n" + " \"some_value\": \"hello\"\n" + " },\n" + " \"basic_information\": {\n" + " \"engagement_types\": {\n" + " \"options\": [\n" + " {\n" + " \"label\": \"Type One\",\n" + " \"value\": \"TypeOne\"\n" @@ -54,7 +61,7 @@ void testGetRuntimeConfigurationTypeOne() { @Test void testGetRuntimeConfigurationTypeTwo() { - given().when().contentType(ContentType.JSON).queryParam("type", "TypeTwo").get(RUNTIME_CONFIG_API).then().statusCode(200).body(is("\n" + "{\n" + " \"more_information\": {\n" + " \"another_value\": \"type two\",\n" + given().auth().oauth2(getValidAccessToken()).when().contentType(ContentType.JSON).queryParam("type", "TypeTwo").get(RUNTIME_CONFIG_API).then().statusCode(200).body(is("\n" + "{\n" + " \"more_information\": {\n" + " \"another_value\": \"type two\",\n" + " \"some_value\": \"hello\"\n" + " },\n" + " \"basic_information\": {\n" + " \"engagement_types\": {\n" + " \"options\": [\n" + " {\n" + " \"label\": \"Type One\",\n" + " \"value\": \"TypeOne\"\n" @@ -72,7 +79,7 @@ void testGetRuntimeConfigurationTypeTwo() { @Test void testGetRuntimeConfigurationTypeUnknown() { - given().when().contentType(ContentType.JSON).queryParam("type", "TypeThree").get(RUNTIME_CONFIG_API).then().statusCode(200).body(is("\n" + "{\n" + " \"more_information\": {\n" + " \"some_value\": \"hello\",\n" + given().auth().oauth2(getValidAccessToken()).when().contentType(ContentType.JSON).queryParam("type", "TypeThree").get(RUNTIME_CONFIG_API).then().statusCode(200).body(is("\n" + "{\n" + " \"more_information\": {\n" + " \"some_value\": \"hello\",\n" + " \"another_value\": \"base\"\n" + " },\n" + " \"basic_information\": {\n" + " \"engagement_types\": {\n" + " \"options\": [\n" + " {\n" + " \"label\": \"Type One\",\n" + " \"value\": \"TypeOne\"\n" @@ -87,5 +94,15 @@ void testGetRuntimeConfigurationTypeUnknown() { + " }\n" + " ]\n" + " }\n" + " }\n" + "}")); } + + private String getValidAccessToken() { + return Jwt.preferredUserName("alice") + .groups(new HashSet<>(Arrays.asList("reader"))) + .issuer("https://quarkus.io/using-jwt-rbac") + .audience("https://quarkus.io/using-jwt-rbac") + .jws() + .keyId("1") + .sign(); + } } diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 9e92a17..0fd7b7f 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -1,2 +1,8 @@ # Runtime Configuration Service configs.runtime.base.file=src/test/resources/base-config.yaml +mp.jwt.verify.publickey=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEqFyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwRTYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5eUF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYnsIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9xnQIDAQAB +# set it to the issuer value which is used to generate the tokens +mp.jwt.verify.issuer=https://quarkus.io/using-jwt-rbac + +# required to sign the tokens +smallrye.jwt.sign.key.location=privateKey.pem diff --git a/src/test/resources/privateKey.pem b/src/test/resources/privateKey.pem new file mode 100644 index 0000000..82e5e9b --- /dev/null +++ b/src/test/resources/privateKey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCWK8UjyoHgPTLa +PLQJ8SoXLLjpHSjtLxMqmzHnFscqhTVVaDpCRCb6e3Ii/WniQTWw8RA7vf4djz4H +OzvlfBFNgvUGZHXDwnmGaNVaNzpHYFMEYBhE8VGGiveSkzqeLZI+Y02G6sQAfDtN +qqzM/l5QX8X34oQFaTBW1r49nftvCpITiwJvWyhkWtXP9RP8sXi1im5Vi3dhupOh +nelk5n0BfajUYIbfHA6ORzjHRbt7NtBl0L2J+0/FUdHyKs6KMlFGNw8O0Dq88qnM +uXoLJiewhg9332W3DFMeOveel+//cvDnRsCRtPgd4sXFPHh+UShkso7+DRsChXa6 +oGGQD3GdAgMBAAECggEAAjfTSZwMHwvIXIDZB+yP+pemg4ryt84iMlbofclQV8hv +6TsI4UGwcbKxFOM5VSYxbNOisb80qasb929gixsyBjsQ8284bhPJR7r0q8h1C+jY +URA6S4pk8d/LmFakXwG9Tz6YPo3pJziuh48lzkFTk0xW2Dp4SLwtAptZY/+ZXyJ6 +96QXDrZKSSM99Jh9s7a0ST66WoxSS0UC51ak+Keb0KJ1jz4bIJ2C3r4rYlSu4hHB +Y73GfkWORtQuyUDa9yDOem0/z0nr6pp+pBSXPLHADsqvZiIhxD/O0Xk5I6/zVHB3 +zuoQqLERk0WvA8FXz2o8AYwcQRY2g30eX9kU4uDQAQKBgQDmf7KGImUGitsEPepF +KH5yLWYWqghHx6wfV+fdbBxoqn9WlwcQ7JbynIiVx8MX8/1lLCCe8v41ypu/eLtP +iY1ev2IKdrUStvYRSsFigRkuPHUo1ajsGHQd+ucTDf58mn7kRLW1JGMeGxo/t32B +m96Af6AiPWPEJuVfgGV0iwg+HQKBgQCmyPzL9M2rhYZn1AozRUguvlpmJHU2DpqS +34Q+7x2Ghf7MgBUhqE0t3FAOxEC7IYBwHmeYOvFR8ZkVRKNF4gbnF9RtLdz0DMEG +5qsMnvJUSQbNB1yVjUCnDAtElqiFRlQ/k0LgYkjKDY7LfciZl9uJRl0OSYeX/qG2 +tRW09tOpgQKBgBSGkpM3RN/MRayfBtmZvYjVWh3yjkI2GbHA1jj1g6IebLB9SnfL +WbXJErCj1U+wvoPf5hfBc7m+jRgD3Eo86YXibQyZfY5pFIh9q7Ll5CQl5hj4zc4Y +b16sFR+xQ1Q9Pcd+BuBWmSz5JOE/qcF869dthgkGhnfVLt/OQzqZluZRAoGAXQ09 +nT0TkmKIvlza5Af/YbTqEpq8mlBDhTYXPlWCD4+qvMWpBII1rSSBtftgcgca9XLB +MXmRMbqtQeRtg4u7dishZVh1MeP7vbHsNLppUQT9Ol6lFPsd2xUpJDc6BkFat62d +Xjr3iWNPC9E9nhPPdCNBv7reX7q81obpeXFMXgECgYEAmk2Qlus3OV0tfoNRqNpe +Mb0teduf2+h3xaI1XDIzPVtZF35ELY/RkAHlmWRT4PCdR0zXDidE67L6XdJyecSt +FdOUH8z5qUraVVebRFvJqf/oGsXc4+ex1ZKUTbY0wqY1y9E39yvB3MaTmZFuuqk8 +f3cg+fr8aou7pr9SHhJlZCU= +-----END PRIVATE KEY----- \ No newline at end of file