diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/EnvironmentVariables.java b/operator/controller/src/main/java/io/apicurio/registry/operator/EnvironmentVariables.java index aa0c4f5f9d..2496fbfe97 100644 --- a/operator/controller/src/main/java/io/apicurio/registry/operator/EnvironmentVariables.java +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/EnvironmentVariables.java @@ -46,6 +46,7 @@ public class EnvironmentVariables { public static final String APICURIO_AUTH_ROLES_DEVELOPER = "APICURIO_AUTH_ROLES_DEVELOPER"; public static final String APICURIO_AUTH_ROLES_READONLY = "APICURIO_AUTH_ROLES_READONLY"; public static final String APICURIO_AUTH_ADMIN_OVERRIDE_ENABLED = "APICURIO_AUTH_ADMIN_OVERRIDE_ENABLED"; + public static final String APICURIO_AUTH_ADMIN_OVERRIDE_ROLE = "APICURIO_AUTH_ADMIN_OVERRIDE_ROLE"; public static final String APICURIO_AUTH_ADMIN_OVERRIDE_FROM = "APICURIO_AUTH_ADMIN_OVERRIDE_FROM"; public static final String APICURIO_AUTH_ADMIN_OVERRIDE_TYPE = "APICURIO_AUTH_ADMIN_OVERRIDE_TYPE"; public static final String APICURIO_AUTH_ADMIN_OVERRIDE_CLAIM = "APICURIO_AUTH_ADMIN_OVERRIDE_CLAIM"; diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/feat/security/AdminOverride.java b/operator/controller/src/main/java/io/apicurio/registry/operator/feat/security/AdminOverride.java index aaf7463d6e..35eb782d8a 100644 --- a/operator/controller/src/main/java/io/apicurio/registry/operator/feat/security/AdminOverride.java +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/feat/security/AdminOverride.java @@ -26,10 +26,13 @@ public static void configureAdminOverride(AdminOverrideSpec adminOverrideSpec, M return; } - if (Boolean.parseBoolean(adminOverrideSpec.getEnabled())) { + if (adminOverrideSpec.getEnabled() != null && adminOverrideSpec.getEnabled()) { env.put(EnvironmentVariables.APICURIO_AUTH_ADMIN_OVERRIDE_ENABLED, createEnvVar(EnvironmentVariables.APICURIO_AUTH_ADMIN_OVERRIDE_ENABLED, - adminOverrideSpec.getEnabled())); + adminOverrideSpec.getEnabled().toString())); + + putIfNotBlank(env, EnvironmentVariables.APICURIO_AUTH_ADMIN_OVERRIDE_ROLE, + adminOverrideSpec.getRole()); putIfNotBlank(env, EnvironmentVariables.APICURIO_AUTH_ADMIN_OVERRIDE_FROM, adminOverrideSpec.getFrom()); diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/feat/security/Auth.java b/operator/controller/src/main/java/io/apicurio/registry/operator/feat/security/Auth.java index ccdd02c669..c57a993a2d 100644 --- a/operator/controller/src/main/java/io/apicurio/registry/operator/feat/security/Auth.java +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/feat/security/Auth.java @@ -40,12 +40,15 @@ public static void configureAuth(AuthSpec authSpec, Deployment deployment, Map env) return; } - if (Boolean.parseBoolean(authzSpec.getEnabled())) { - env.put(EnvironmentVariables.APICURIO_AUTH_ROLE_BASED_AUTHORIZATION, createEnvVar( - EnvironmentVariables.APICURIO_AUTH_ROLE_BASED_AUTHORIZATION, authzSpec.getEnabled())); - - putIfNotBlank(env, EnvironmentVariables.APICURIO_AUTH_OWNER_ONLY_AUTHORIZATION, - authzSpec.getOwnerOnly()); - putIfNotBlank(env, EnvironmentVariables.APICURIO_AUTH_OWNER_ONLY_AUTHORIZATION_LIMIT_GROUP_ACCESS, - authzSpec.getGroupAccess()); - putIfNotBlank(env, EnvironmentVariables.APICURIO_AUTH_AUTHENTICATED_READ_ACCESS_ENABLED, - authzSpec.getReadAccess()); + if (authzSpec.getEnabled()) { + env.put(EnvironmentVariables.APICURIO_AUTH_ROLE_BASED_AUTHORIZATION, + createEnvVar(EnvironmentVariables.APICURIO_AUTH_ROLE_BASED_AUTHORIZATION, + authzSpec.getEnabled().toString())); + + if (authzSpec.getGroupAccess() != null && authzSpec.getGroupAccess()) { + putIfNotBlank(env, + EnvironmentVariables.APICURIO_AUTH_OWNER_ONLY_AUTHORIZATION_LIMIT_GROUP_ACCESS, + authzSpec.getGroupAccess().toString()); + } + + if (authzSpec.getOwnerOnly() != null && authzSpec.getOwnerOnly()) { + putIfNotBlank(env, EnvironmentVariables.APICURIO_AUTH_OWNER_ONLY_AUTHORIZATION, + authzSpec.getOwnerOnly().toString()); + } + + if (authzSpec.getReadAccess() != null && authzSpec.getReadAccess()) { + putIfNotBlank(env, EnvironmentVariables.APICURIO_AUTH_AUTHENTICATED_READ_ACCESS_ENABLED, + authzSpec.getReadAccess().toString()); + } + putIfNotBlank(env, EnvironmentVariables.APICURIO_AUTH_ROLE_SOURCE, authzSpec.getRoleSource()); - putIfNotBlank(env, EnvironmentVariables.APICURIO_AUTH_ROLES_ADMIN, authzSpec.getDeveloperRole()); + putIfNotBlank(env, EnvironmentVariables.APICURIO_AUTH_ROLES_ADMIN, authzSpec.getAdminRole()); putIfNotBlank(env, EnvironmentVariables.APICURIO_AUTH_ROLES_DEVELOPER, authzSpec.getDeveloperRole()); putIfNotBlank(env, EnvironmentVariables.APICURIO_AUTH_ROLES_READONLY, diff --git a/operator/controller/src/test/java/io/apicurio/registry/operator/it/KeycloakITTest.java b/operator/controller/src/test/java/io/apicurio/registry/operator/it/AuthITTest.java similarity index 63% rename from operator/controller/src/test/java/io/apicurio/registry/operator/it/KeycloakITTest.java rename to operator/controller/src/test/java/io/apicurio/registry/operator/it/AuthITTest.java index e8412bcb63..a36b24a683 100644 --- a/operator/controller/src/test/java/io/apicurio/registry/operator/it/KeycloakITTest.java +++ b/operator/controller/src/test/java/io/apicurio/registry/operator/it/AuthITTest.java @@ -2,36 +2,19 @@ import io.apicurio.registry.operator.EnvironmentVariables; import io.apicurio.registry.operator.api.v1.ApicurioRegistry3; -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.client.utils.Serialization; +import io.apicurio.registry.operator.api.v1.spec.auth.AuthSpec; import io.quarkus.test.junit.QuarkusTest; -import org.awaitility.Awaitility; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.Duration; -import java.util.List; import static io.apicurio.registry.operator.api.v1.ContainerNames.REGISTRY_APP_CONTAINER_NAME; import static io.apicurio.registry.operator.resource.ResourceFactory.COMPONENT_APP; import static io.apicurio.registry.operator.resource.ResourceFactory.COMPONENT_UI; -import static io.apicurio.registry.operator.resource.ResourceFactory.deserialize; import static io.apicurio.registry.operator.resource.app.AppDeploymentResource.getContainerFromDeployment; import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; @QuarkusTest -public class KeycloakITTest extends ITBase { - - private static final Logger log = LoggerFactory.getLogger(KeycloakITTest.class); - - @BeforeAll - public static void init() { - Awaitility.setDefaultTimeout(Duration.ofSeconds(60)); - } +public class AuthITTest extends BaseAuthTest { /** * In this test, Keycloak is deployed using a self-signed certificate with the hostname set to the ingress @@ -39,36 +22,21 @@ public static void init() { * works. */ @Test - void testKeycloakPlain() { + void testAuthTlsNoVerification() { // Preparation, deploy Keycloak - List resources = Serialization - .unmarshal(KeycloakITTest.class.getResourceAsStream("/k8s/examples/auth/keycloak.yaml")); - - createResources(resources, "Keycloak"); - - await().ignoreExceptions().untilAsserted(() -> { - assertThat(client.apps().deployments().withName("keycloak").get().getStatus().getReadyReplicas()) - .isEqualTo(1); - }); - - createKeycloakDNSResolution("simple-keycloak.apps.cluster.example", - "keycloak." + namespace + ".svc.cluster.local"); + ApicurioRegistry3 registry = prepareInfra("/k8s/examples/auth/keycloak.yaml", + "k8s/examples/auth/simple-with_keycloak.apicurioregistry3.yaml"); + AuthSpec authSpec = registry.getSpec().getApp().getAuth(); - // Deploy Registry - var registry = deserialize("k8s/examples/auth/simple-with_keycloak.apicurioregistry3.yaml", - ApicurioRegistry3.class); - - registry.getMetadata().setNamespace(namespace); - - var appAuthSpec = registry.getSpec().getApp().getAuth(); - - Assertions.assertEquals("registry-api", appAuthSpec.getAppClientId()); - Assertions.assertEquals("apicurio-registry", appAuthSpec.getUiClientId()); - Assertions.assertEquals(true, appAuthSpec.getEnabled()); + Assertions.assertEquals("registry-api", authSpec.getAppClientId()); + Assertions.assertEquals("apicurio-registry", authSpec.getUiClientId()); + Assertions.assertEquals(true, authSpec.getEnabled()); Assertions.assertEquals("https://simple-keycloak.apps.cluster.example/realms/registry", - appAuthSpec.getAuthServerUrl()); - Assertions.assertEquals("https://simple-ui.apps.cluster.example", appAuthSpec.getRedirectURI()); - Assertions.assertEquals("https://simple-ui.apps.cluster.example", appAuthSpec.getLogoutURL()); + authSpec.getAuthServerUrl()); + Assertions.assertEquals("https://simple-ui.apps.cluster.example", authSpec.getRedirectURI()); + Assertions.assertEquals("https://simple-ui.apps.cluster.example", authSpec.getLogoutURL()); + + Assertions.assertEquals("https://simple-ui.apps.cluster.example", authSpec.getLogoutURL()); client.resource(registry).create(); @@ -101,5 +69,12 @@ void testKeycloakPlain() { + "https://simple-ui.apps.cluster.example"); assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) .contains(EnvironmentVariables.OIDC_TLS_VERIFICATION + "=" + "none"); + + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()).contains( + EnvironmentVariables.APICURIO_AUTHN_BASIC_CLIENT_CREDENTIALS_ENABLED + "=" + "true"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()).contains( + EnvironmentVariables.APICURIO_AUTHN_BASIC_CLIENT_CREDENTIALS_CACHE_EXPIRATION + "=" + "25"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_AUTH_ANONYMOUS_READ_ACCESS_ENABLED + "=" + "true"); } } diff --git a/operator/controller/src/test/java/io/apicurio/registry/operator/it/KeycloakTLSITTest.java b/operator/controller/src/test/java/io/apicurio/registry/operator/it/AuthTLSITTest.java similarity index 68% rename from operator/controller/src/test/java/io/apicurio/registry/operator/it/KeycloakTLSITTest.java rename to operator/controller/src/test/java/io/apicurio/registry/operator/it/AuthTLSITTest.java index d46d18f419..078e622a08 100644 --- a/operator/controller/src/test/java/io/apicurio/registry/operator/it/KeycloakTLSITTest.java +++ b/operator/controller/src/test/java/io/apicurio/registry/operator/it/AuthTLSITTest.java @@ -2,8 +2,7 @@ import io.apicurio.registry.operator.EnvironmentVariables; import io.apicurio.registry.operator.api.v1.ApicurioRegistry3; -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.client.utils.Serialization; +import io.apicurio.registry.operator.api.v1.spec.auth.AuthSpec; import io.quarkus.test.junit.QuarkusTest; import org.awaitility.Awaitility; import org.junit.jupiter.api.Assertions; @@ -13,20 +12,17 @@ import org.slf4j.LoggerFactory; import java.time.Duration; -import java.util.List; import static io.apicurio.registry.operator.api.v1.ContainerNames.REGISTRY_APP_CONTAINER_NAME; import static io.apicurio.registry.operator.resource.ResourceFactory.COMPONENT_APP; import static io.apicurio.registry.operator.resource.ResourceFactory.COMPONENT_UI; -import static io.apicurio.registry.operator.resource.ResourceFactory.deserialize; import static io.apicurio.registry.operator.resource.app.AppDeploymentResource.getContainerFromDeployment; import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; @QuarkusTest -public class KeycloakTLSITTest extends ITBase { +public class AuthTLSITTest extends BaseAuthTest { - private static final Logger log = LoggerFactory.getLogger(KeycloakTLSITTest.class); + private static final Logger log = LoggerFactory.getLogger(AuthTLSITTest.class); @BeforeAll public static void init() { @@ -39,36 +35,18 @@ public static void init() { * Quarkus application using the custom resource. */ @Test - void testKeycloakTLS() { - // Preparation, deploy Keycloak - List resources = Serialization - .unmarshal(KeycloakTLSITTest.class.getResourceAsStream("/k8s/examples/auth/keycloak.yaml")); - - createResources(resources, "Keycloak"); - - await().ignoreExceptions().untilAsserted(() -> { - assertThat(client.apps().deployments().withName("keycloak").get().getStatus().getReadyReplicas()) - .isEqualTo(1); - }); - - createKeycloakDNSResolution("simple-keycloak.apps.cluster.example", - "keycloak." + namespace + ".svc.cluster.local"); - - // Deploy Registry - var registry = deserialize("k8s/examples/auth/tls/simple-with_keycloak.apicurioregistry3.yaml", - ApicurioRegistry3.class); - - registry.getMetadata().setNamespace(namespace); - - var appAuthSpec = registry.getSpec().getApp().getAuth(); - - Assertions.assertEquals("registry-api", appAuthSpec.getAppClientId()); - Assertions.assertEquals("apicurio-registry", appAuthSpec.getUiClientId()); - Assertions.assertEquals(true, appAuthSpec.getEnabled()); + void testAuthTlsVerification() { + ApicurioRegistry3 registry = prepareInfra("/k8s/examples/auth/keycloak.yaml", + "k8s/examples/auth/tls/simple-with_keycloak.apicurioregistry3.yaml"); + AuthSpec authSpec = registry.getSpec().getApp().getAuth(); + + Assertions.assertEquals("registry-api", authSpec.getAppClientId()); + Assertions.assertEquals("apicurio-registry", authSpec.getUiClientId()); + Assertions.assertEquals(true, authSpec.getEnabled()); Assertions.assertEquals("https://simple-keycloak.apps.cluster.example/realms/registry", - appAuthSpec.getAuthServerUrl()); - Assertions.assertEquals("https://simple-ui.apps.cluster.example", appAuthSpec.getRedirectURI()); - Assertions.assertEquals("https://simple-ui.apps.cluster.example", appAuthSpec.getLogoutURL()); + authSpec.getAuthServerUrl()); + Assertions.assertEquals("https://simple-ui.apps.cluster.example", authSpec.getRedirectURI()); + Assertions.assertEquals("https://simple-ui.apps.cluster.example", authSpec.getLogoutURL()); client.resource(registry).create(); diff --git a/operator/controller/src/test/java/io/apicurio/registry/operator/it/AuthzITTest.java b/operator/controller/src/test/java/io/apicurio/registry/operator/it/AuthzITTest.java new file mode 100644 index 0000000000..41a6a7e6a6 --- /dev/null +++ b/operator/controller/src/test/java/io/apicurio/registry/operator/it/AuthzITTest.java @@ -0,0 +1,131 @@ +package io.apicurio.registry.operator.it; + +import io.apicurio.registry.operator.EnvironmentVariables; +import io.apicurio.registry.operator.api.v1.ApicurioRegistry3; +import io.apicurio.registry.operator.api.v1.spec.auth.AdminOverrideSpec; +import io.apicurio.registry.operator.api.v1.spec.auth.AuthSpec; +import io.apicurio.registry.operator.api.v1.spec.auth.AuthzSpec; +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static io.apicurio.registry.operator.api.v1.ContainerNames.REGISTRY_APP_CONTAINER_NAME; +import static io.apicurio.registry.operator.resource.ResourceFactory.COMPONENT_APP; +import static io.apicurio.registry.operator.resource.ResourceFactory.COMPONENT_UI; +import static io.apicurio.registry.operator.resource.app.AppDeploymentResource.getContainerFromDeployment; +import static org.assertj.core.api.Assertions.assertThat; + +@QuarkusTest +public class AuthzITTest extends BaseAuthTest { + + /** + * In this test, Keycloak is deployed using a self-signed certificate with the hostname set to the ingress + * value. TLS verification is disabled at the Apicurio Registry level, so even in that case the deployment + * works. + */ + @Test + void testAuthz() { + // Preparation, deploy Keycloak + ApicurioRegistry3 registry = prepareInfra("/k8s/examples/auth/keycloak.yaml", + "k8s/examples/auth/authz-with_keycloak.apicurioregistry3.yaml"); + AuthSpec authSpec = registry.getSpec().getApp().getAuth(); + + Assertions.assertEquals("registry-api", authSpec.getAppClientId()); + Assertions.assertEquals("apicurio-registry", authSpec.getUiClientId()); + Assertions.assertEquals(true, authSpec.getEnabled()); + Assertions.assertEquals("https://simple-keycloak.apps.cluster.example/realms/registry", + authSpec.getAuthServerUrl()); + Assertions.assertEquals("https://simple-ui.apps.cluster.example", authSpec.getRedirectURI()); + Assertions.assertEquals("https://simple-ui.apps.cluster.example", authSpec.getLogoutURL()); + + AuthzSpec authzSpec = authSpec.getAuthz(); + + // Authz exclusive assertions + Assertions.assertEquals(true, authzSpec.getEnabled()); + Assertions.assertEquals(true, authzSpec.getOwnerOnly()); + Assertions.assertEquals(true, authzSpec.getGroupAccess()); + Assertions.assertEquals(true, authzSpec.getReadAccess()); + Assertions.assertEquals("token", authzSpec.getRoleSource()); + Assertions.assertEquals("admin", authzSpec.getAdminRole()); + Assertions.assertEquals("dev", authzSpec.getDeveloperRole()); + Assertions.assertEquals("read", authzSpec.getReadOnlyRole()); + + // Admin Override assertions + AdminOverrideSpec adminOverrideSpec = authzSpec.getAdminOverride(); + Assertions.assertEquals(true, adminOverrideSpec.getEnabled()); + Assertions.assertEquals("token", adminOverrideSpec.getFrom()); + Assertions.assertEquals("claim", adminOverrideSpec.getType()); + Assertions.assertEquals("admin", adminOverrideSpec.getRole()); + Assertions.assertEquals("test", adminOverrideSpec.getClaimName()); + Assertions.assertEquals("test", adminOverrideSpec.getClaimValue()); + + client.resource(registry).create(); + + // Assertions, checks registry deployments exist + checkDeploymentExists(registry, COMPONENT_APP, 1); + checkDeploymentExists(registry, COMPONENT_UI, 1); + + // App deployment auth related assertions + var appEnv = getContainerFromDeployment( + client.apps().deployments().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-app-deployment").get(), + REGISTRY_APP_CONTAINER_NAME).getEnv(); + + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_REGISTRY_AUTH_ENABLED + "=" + "true"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.OIDC_TLS_VERIFICATION + "=" + "required"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_REGISTRY_APP_CLIENT_ID + "=" + "registry-api"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_REGISTRY_UI_CLIENT_ID + "=" + "apicurio-registry"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_REGISTRY_AUTH_SERVER_URL + "=" + + "https://simple-keycloak.apps.cluster.example/realms/registry"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_UI_AUTH_OIDC_REDIRECT_URI + "=" + + "https://simple-ui.apps.cluster.example"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_UI_AUTH_OIDC_LOGOUT_URL + "=" + + "https://simple-ui.apps.cluster.example"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.OIDC_TLS_VERIFICATION + "=" + "required"); + + // Authz exclusive assertions + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_AUTH_ROLE_BASED_AUTHORIZATION + "=" + "true"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()).contains( + EnvironmentVariables.APICURIO_AUTH_AUTHENTICATED_READ_ACCESS_ENABLED + "=" + "true"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_AUTH_OWNER_ONLY_AUTHORIZATION_LIMIT_GROUP_ACCESS + "=" + + "true"); + + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_AUTH_OWNER_ONLY_AUTHORIZATION + "=" + "true"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_AUTH_ROLE_SOURCE + "=" + "token"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_AUTH_ROLES_ADMIN + "=" + "admin"); + + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_AUTH_ROLES_DEVELOPER + "=" + "dev"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_AUTH_ROLES_READONLY + "=" + "read"); + + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_AUTH_ADMIN_OVERRIDE_ENABLED + "=" + "true"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_AUTH_ADMIN_OVERRIDE_FROM + "=" + "token"); + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_AUTH_ADMIN_OVERRIDE_TYPE + "=" + "claim"); + + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_AUTH_ADMIN_OVERRIDE_ROLE + "=" + "admin"); + + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_AUTH_ADMIN_OVERRIDE_CLAIM + "=" + "test"); + + assertThat(appEnv).map(ev -> ev.getName() + "=" + ev.getValue()) + .contains(EnvironmentVariables.APICURIO_AUTH_ADMIN_OVERRIDE_CLAIM_VALUE + "=" + "test"); + } +} diff --git a/operator/controller/src/test/java/io/apicurio/registry/operator/it/BaseAuthTest.java b/operator/controller/src/test/java/io/apicurio/registry/operator/it/BaseAuthTest.java new file mode 100644 index 0000000000..f92da4a9f8 --- /dev/null +++ b/operator/controller/src/test/java/io/apicurio/registry/operator/it/BaseAuthTest.java @@ -0,0 +1,44 @@ +package io.apicurio.registry.operator.it; + +import io.apicurio.registry.operator.api.v1.ApicurioRegistry3; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.utils.Serialization; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.BeforeAll; + +import java.time.Duration; +import java.util.List; + +import static io.apicurio.registry.operator.resource.ResourceFactory.deserialize; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public abstract class BaseAuthTest extends ITBase { + + @BeforeAll + public static void init() { + Awaitility.setDefaultTimeout(Duration.ofSeconds(60)); + } + + protected static ApicurioRegistry3 prepareInfra(String keycloakResource, String apicurioResource) { + List resources = Serialization + .unmarshal(AuthITTest.class.getResourceAsStream(keycloakResource)); + + createResources(resources, "Keycloak"); + + await().ignoreExceptions().untilAsserted(() -> { + assertThat(client.apps().deployments().withName("keycloak").get().getStatus().getReadyReplicas()) + .isEqualTo(1); + }); + + createKeycloakDNSResolution("simple-keycloak.apps.cluster.example", + "keycloak." + namespace + ".svc.cluster.local"); + + // Deploy Registry + var registry = deserialize(apicurioResource, ApicurioRegistry3.class); + + registry.getMetadata().setNamespace(namespace); + + return registry; + } +} diff --git a/operator/controller/src/test/resources/k8s/examples/auth/authz-with_keycloak.apicurioregistry3.yaml b/operator/controller/src/test/resources/k8s/examples/auth/authz-with_keycloak.apicurioregistry3.yaml new file mode 100644 index 0000000000..9c4313a96c --- /dev/null +++ b/operator/controller/src/test/resources/k8s/examples/auth/authz-with_keycloak.apicurioregistry3.yaml @@ -0,0 +1,42 @@ +apiVersion: registry.apicur.io/v1 +kind: ApicurioRegistry3 +metadata: + name: simple +spec: + app: + ingress: + host: simple-app.apps.cluster.example + auth: + enabled: true + appClientId: registry-api + uiClientId: apicurio-registry + authServerUrl: https://simple-keycloak.apps.cluster.example/realms/registry + redirectURI: https://simple-ui.apps.cluster.example + logoutURL: https://simple-ui.apps.cluster.example + authz: + enabled: true + ownerOnly: true + groupAccess: true + readAccess: true + roleSource: token + adminRole: admin + developerRole: dev + readOnlyRole: read + adminOverride: + enabled: true + from: token + type: claim + role: admin + claimName: test + claimValue: test + tls: + tlsVerificationType: required + truststoreSecretRef: + name: keycloak-truststore + key: truststore + truststorePasswordSecretRef: + name: keycloak-truststore + key: password + ui: + ingress: + host: simple-ui.apps.cluster.example diff --git a/operator/controller/src/test/resources/k8s/examples/auth/simple-with_keycloak.apicurioregistry3.yaml b/operator/controller/src/test/resources/k8s/examples/auth/simple-with_keycloak.apicurioregistry3.yaml index 2e49ddeae0..81a2d74418 100644 --- a/operator/controller/src/test/resources/k8s/examples/auth/simple-with_keycloak.apicurioregistry3.yaml +++ b/operator/controller/src/test/resources/k8s/examples/auth/simple-with_keycloak.apicurioregistry3.yaml @@ -13,6 +13,10 @@ spec: authServerUrl: https://simple-keycloak.apps.cluster.example/realms/registry redirectURI: https://simple-ui.apps.cluster.example logoutURL: https://simple-ui.apps.cluster.example + anonymousReads: true + basicAuth: + enabled: true + cacheExpiration: 25 tls: tlsVerificationType: none ui: diff --git a/operator/install/install.yaml b/operator/install/install.yaml index a63fcd7ed1..b4261e716f 100644 --- a/operator/install/install.yaml +++ b/operator/install/install.yaml @@ -31,6 +31,11 @@ spec: description: | Configure authentication and authorization of Apicurio Registry. properties: + anonymousReads: + description: To allow anonymous users, such as REST API calls + with no authentication credentials, to make read-only calls + to the REST API, set the following option to true. + type: boolean appClientId: description: |- Apicurio Registry backend clientId used for OIDC authentication. @@ -39,11 +44,135 @@ spec: authServerUrl: description: URL of the identity server. type: string + authz: + description: Authorization configuration. + properties: + adminOverride: + description: Admin override configuration + properties: + claimName: + description: The name of a JWT token claim to use + for determining admin-override. + type: string + claimValue: + description: The value that the JWT token claim indicated + by the CLAIM variable must be for the user to be + granted admin-override. + type: string + enabled: + description: Auth admin override enabled. + type: boolean + from: + description: Where to look for admin-override information. + Only token is currently supported. + type: string + role: + description: The name of the role that indicates a + user is an admin. + type: string + type: + description: The type of information used to determine + if a user is an admin. Values depend on the value + of the FROM variable, for example, role or claim + when FROM is token. + type: string + type: object + adminRole: + description: The name of the role that indicates a user + is an admin. + type: string + developerRole: + description: The name of the role that indicates a user + is a developer. + type: string + enabled: + description: Enabled role-based authorization. + type: boolean + groupAccess: + description: When owner-only authorization and group owner-only + authorization are both enabled, only the user who created + an artifact group has write access to that artifact + group, for example, to add or remove artifacts in that + group. + type: boolean + ownerOnly: + description: When owner-only authorization is enabled, + only the user who created an artifact can modify or + delete that artifact. + type: boolean + readAccess: + description: When the authenticated read access option + is enabled, Apicurio Registry grants at least read-only + access to requests from any authenticated user in the + same organization, regardless of their user role. + type: boolean + readOnlyRole: + description: The name of the role that indicates a user + has read-only access. + type: string + roleSource: + description: When set to token, user roles are taken from + the authentication token. + type: string + type: object + basicAuth: + description: Client credentials basic auth configuration. + properties: + cacheExpiration: + description: Client credentials token expiration time. + type: string + enabled: + description: Enabled client credentials. + type: boolean + type: object enabled: description: |- Enable Apicurio Registry Authentication. In Identity providers like Keycloak, this is the client id used for the Quarkus backend application type: boolean + logoutURL: + description: Apicurio Registry UI redirect URI used for redirection + after logout. + type: string + redirectURI: + description: Apicurio Registry UI redirect URI used for redirection + after successful authentication. + type: string + tls: + description: |- + OIDC TLS configuration. + When custom certificates are used, this is the field to be used to configure the trustore + properties: + tlsVerificationType: + description: Verify the identity server certificate. + type: string + truststorePasswordSecretRef: + description: Name of a Secret that contains the TLS truststore + password. Key `ca.password` is assumed by default. + properties: + key: + description: Name of the key in the referenced Secret + that contain the target data. This field might be + optional if a default value has been defined. + type: string + name: + description: Name of a Secret that is being referenced. + type: string + type: object + truststoreSecretRef: + description: Name of a Secret that contains the TLS truststore + (in PKCS12 format). Key `ca.p12` is assumed by default. + properties: + key: + description: Name of the key in the referenced Secret + that contain the target data. This field might be + optional if a default value has been defined. + type: string + name: + description: Name of a Secret that is being referenced. + type: string + type: object + type: object uiClientId: description: |- Apicurio Registry UI clientId used for OIDC authentication. diff --git a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/auth/AdminOverrideSpec.java b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/auth/AdminOverrideSpec.java index cefc85b83f..d68f3bca61 100644 --- a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/auth/AdminOverrideSpec.java +++ b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/auth/AdminOverrideSpec.java @@ -35,7 +35,7 @@ public class AdminOverrideSpec { @JsonPropertyDescription(""" Auth admin override enabled.""") @JsonSetter(nulls = Nulls.SKIP) - private String enabled; + private Boolean enabled; @JsonProperty("from") @JsonPropertyDescription(""" diff --git a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/auth/AuthzSpec.java b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/auth/AuthzSpec.java index a423153c69..805921b1ed 100644 --- a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/auth/AuthzSpec.java +++ b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/auth/AuthzSpec.java @@ -35,25 +35,25 @@ public class AuthzSpec { @JsonPropertyDescription(""" Enabled role-based authorization.""") @JsonSetter(nulls = Nulls.SKIP) - private String enabled; + private Boolean enabled; @JsonProperty("ownerOnly") @JsonPropertyDescription(""" When owner-only authorization is enabled, only the user who created an artifact can modify or delete that artifact.""") @JsonSetter(nulls = Nulls.SKIP) - private String ownerOnly; + private Boolean ownerOnly; @JsonProperty("groupAccess") @JsonPropertyDescription(""" When owner-only authorization and group owner-only authorization are both enabled, only the user who created an artifact group has write access to that artifact group, for example, to add or remove artifacts in that group.""") @JsonSetter(nulls = Nulls.SKIP) - private String groupAccess; + private Boolean groupAccess; @JsonProperty("readAccess") @JsonPropertyDescription(""" When the authenticated read access option is enabled, Apicurio Registry grants at least read-only access to requests from any authenticated user in the same organization, regardless of their user role.""") @JsonSetter(nulls = Nulls.SKIP) - private String readAccess; + private Boolean readAccess; @JsonProperty("roleSource") @JsonPropertyDescription(""" @@ -61,7 +61,7 @@ public class AuthzSpec { @JsonSetter(nulls = Nulls.SKIP) private String roleSource; - @JsonProperty("roleSource") + @JsonProperty("adminRole") @JsonPropertyDescription(""" The name of the role that indicates a user is an admin.""") @JsonSetter(nulls = Nulls.SKIP) diff --git a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/auth/BasicAuthSpec.java b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/auth/BasicAuthSpec.java index 083324154c..aa781df86f 100644 --- a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/auth/BasicAuthSpec.java +++ b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/auth/BasicAuthSpec.java @@ -35,7 +35,7 @@ public class BasicAuthSpec { @JsonPropertyDescription(""" Enabled client credentials.""") @JsonSetter(nulls = Nulls.SKIP) - private String enabled; + private Boolean enabled; @JsonProperty("cacheExpiration") @JsonPropertyDescription("""