diff --git a/.gitignore b/.gitignore index 9f49f1b..dfaed2b 100644 --- a/.gitignore +++ b/.gitignore @@ -387,3 +387,14 @@ FodyWeavers.xsd .idea/ *.sln.iml *launchSettings.json + +# integrationtest state files and generated directories +/integration_tests/terraform/*/.terraform/ +/integration_tests/terraform/*/terraform.tfstate* +/integration_tests/terraform/*/.terraform.lock.hcl + +# codegen temporary files +pkg/openapi.json* + +# integration tsts dependency charts +/integration_tests/helm/setup/charts/*.tgz diff --git a/integration_tests/helm/setup/.helmignore b/integration_tests/helm/setup/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/integration_tests/helm/setup/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/integration_tests/helm/setup/Chart.lock b/integration_tests/helm/setup/Chart.lock new file mode 100644 index 0000000..1bcfb94 --- /dev/null +++ b/integration_tests/helm/setup/Chart.lock @@ -0,0 +1,12 @@ +dependencies: +- name: boxer-crd + repository: oci://ghcr.io/sneaksanddata/helm + version: v0.0.5 +- name: boxer-issuer + repository: oci://ghcr.io/sneaksanddata/helm + version: v0.0.14 +- name: boxer-validator-nginx + repository: oci://ghcr.io/sneaksanddata/helm + version: v0.0.15 +digest: sha256:5f1192696ab1c7824862f2024e691105b705b0a34bfaa8075b2873ecaa595b06 +generated: "2025-10-24T14:53:24.367292+02:00" diff --git a/integration_tests/helm/setup/Chart.yaml b/integration_tests/helm/setup/Chart.yaml new file mode 100644 index 0000000..4ac3621 --- /dev/null +++ b/integration_tests/helm/setup/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: setup +description: Integration test setup chart for terraform-provider-boxer +type: application +version: 0.0.1 +appVersion: "0.0.1" +dependencies: + - name: boxer-crd + version: "v0.0.5" + repository: "oci://ghcr.io/sneaksanddata/helm" + - name: boxer-issuer + version: "v0.0.14" + repository: "oci://ghcr.io/sneaksanddata/helm" + - name: boxer-validator-nginx + version: "v0.0.15" + repository: "oci://ghcr.io/sneaksanddata/helm" + + diff --git a/integration_tests/helm/setup/templates/NOTES.txt b/integration_tests/helm/setup/templates/NOTES.txt new file mode 100644 index 0000000..7e840d1 --- /dev/null +++ b/integration_tests/helm/setup/templates/NOTES.txt @@ -0,0 +1,4 @@ +IMPORTANT NOTE: +This chart is designed to be used in integration tests and is not intended for production use. +If you want to install the Boxer Issuer application, please use the Boxer Issuer chart instead. +The boxer issuer chart can be found at: https://github.com/SneaksAndData/boxer-crd/pkgs/container/helm%2Fboxer-crd \ No newline at end of file diff --git a/integration_tests/helm/setup/templates/_helpers.tpl b/integration_tests/helm/setup/templates/_helpers.tpl new file mode 100644 index 0000000..2d41150 --- /dev/null +++ b/integration_tests/helm/setup/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "integration_tests.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "integration_tests.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "integration_tests.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "integration_tests.labels" -}} +helm.sh/chart: {{ include "integration_tests.chart" . }} +{{ include "integration_tests.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "integration_tests.selectorLabels" -}} +app.kubernetes.io/name: {{ include "integration_tests.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "integration_tests.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "integration_tests.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/integration_tests/helm/setup/values.yaml b/integration_tests/helm/setup/values.yaml new file mode 100644 index 0000000..e69de29 diff --git a/integration_tests/keycloak.yaml b/integration_tests/keycloak.yaml new file mode 100644 index 0000000..c55e7b1 --- /dev/null +++ b/integration_tests/keycloak.yaml @@ -0,0 +1,13 @@ +command: + - "/opt/keycloak/bin/kc.sh" + - "start" + - "--http-port=8080" + - "--hostname-strict=false" +extraEnv: | + - name: KEYCLOAK_ADMIN + value: admin + - name: KEYCLOAK_ADMIN_PASSWORD + value: admin + - name: JAVA_OPTS_APPEND + value: >- + -Djgroups.dns.query={{ include "keycloak.fullname" . }}-headless \ No newline at end of file diff --git a/integration_tests/manifests/ingress.yaml b/integration_tests/manifests/ingress.yaml index bc69ec2..ad9b8cd 100644 --- a/integration_tests/manifests/ingress.yaml +++ b/integration_tests/manifests/ingress.yaml @@ -37,3 +37,47 @@ spec: name: nexus port: number: 8080 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: boxer-ingress + annotations: + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/rewrite-target: /$2 +spec: + ingressClassName: nginx + rules: + - http: + paths: + - pathType: ImplementationSpecific + path: /issuer(/|$)(.*) + backend: + service: + name: boxer-issuer + port: + number: 80 + - pathType: ImplementationSpecific + path: /validator(/|$)(.*) + backend: + service: + name: boxer-validator-nginx + port: + number: 80 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: keycloak-ingress +spec: + ingressClassName: nginx + rules: + - http: + paths: + - pathType: Prefix + path: /auth + backend: + service: + name: keycloak-keycloakx-http + port: + number: 80 diff --git a/integration_tests/terraform/keycloak/keycloak.tf b/integration_tests/terraform/keycloak/keycloak.tf new file mode 100644 index 0000000..8ad0629 --- /dev/null +++ b/integration_tests/terraform/keycloak/keycloak.tf @@ -0,0 +1,62 @@ +terraform { + required_providers { + keycloak = { + source = "keycloak/keycloak" + version = "= 5.0.0" + } + } +} + +provider "keycloak" { + client_id = "admin-cli" + username = "admin" + password = "admin" + url = "http://localhost:5555/auth" +} + +data "keycloak_realm" "master" { + realm = "master" +} + +resource "keycloak_openid_client" "test_client" { + name = "test_client" + access_type = "PUBLIC" + client_id = "test_client" + client_secret = "test_client_secret" + realm_id = data.keycloak_realm.master.id + enabled = true + direct_access_grants_enabled = true +} + +resource "keycloak_openid_client_default_scopes" "client_default_scopes" { + realm_id = data.keycloak_realm.master.id + client_id = keycloak_openid_client.test_client.id + + default_scopes = [ + "profile", + "email", + "roles", + "web-origins", + ] +} + +resource "keycloak_user" "test_user" { + realm_id = data.keycloak_realm.master.id + username = "test_user" + enabled = true + + initial_password { + value = "test-user-password" + } +} + +resource "keycloak_user" "test_root" { + realm_id = data.keycloak_realm.master.id + username = "test_root" + enabled = true + + initial_password { + value = "test-root-password" + } +} + diff --git a/justfile b/justfile index 47376b6..4845dd9 100644 --- a/justfile +++ b/justfile @@ -4,6 +4,7 @@ default: fresh: stop up up: start-kind-cluster \ + build-deps \ install-ingress-controller \ create-ingress \ scylla \ @@ -15,7 +16,10 @@ up: start-kind-cluster \ receiver \ supervisor \ minio \ - wait-for-services + boxer \ + keycloak \ + wait-for-services \ + keycloak-configuration stop: kind delete cluster @@ -23,6 +27,9 @@ stop: start-kind-cluster: kind create cluster --config=integration_tests/kind.yaml +build-deps: + helm dependency build ./integration_tests/helm/setup + install-ingress-controller: kubectl apply -f https://kind.sigs.k8s.io/examples/ingress/deploy-ingress-nginx.yaml kubectl rollout status deployment/ingress-nginx-controller -n ingress-nginx --timeout=180s @@ -88,11 +95,42 @@ supervisor: --set 'supervisor.config.cqlStore.secretRefEnabled=true' \ --set 'supervisor.config.cqlStore.secretName=cassandra-credentials' \ --set 'supervisor.config.resourceNamespace=default' + + +key := `openssl rand -base64 16 | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1` +boxer: + helm upgrade --install --namespace default integration-tests integration_tests/helm/setup \ + --set boxer-issuer.issuer.replicas=1 \ + --set-literal 'boxer-validator-nginx.validator.config.tokenSettings.keys={"default": "{{key}}"}' \ + --set 'boxer-issuer.issuer.config.listenIp=0.0.0.0' \ + --set 'boxer-issuer.issuer.config.logLevel=debug' \ + --set 'boxer-issuer.issuer.config.backend.kubernetes.resourceOwnerLabel=application/boxer-issuer' \ + --set 'boxer-validator-nginx.validator.config.listenIp=0.0.0.0' \ + --set 'boxer-validator-nginx.validator.config.backend.kubernetes.resourceOwnerLabel=application/boxer-validator-nginx' \ + --set boxer-validator-nginx.validator.replicas=1 + +keycloak: + helm upgrade --install keycloak oci://ghcr.io/codecentric/helm-charts/keycloakx \ + --set keycloak.username=admin \ + --set keycloak.password=admin \ + --values ./integration_tests/keycloak.yaml wait-for-services: kubectl rollout status deployment/nexus --timeout=180s kubectl rollout status deployment/nexus-receiver --timeout=180s kubectl rollout status deployment/nexus-supervisor --timeout=180s + kubectl rollout status deployment/boxer-issuer --timeout=180s + kubectl rollout status deployment/boxer-validator-nginx --timeout=180s + +keycloak-configuration: + # Wait a bit for Keycloak to be ready to accept admin commands + sleep 10 + + # Create realm, client, and user for tests + docker run --rm --network=host -v $(pwd)/integration_tests/terraform/keycloak:/tofu --workdir /tofu ghcr.io/opentofu/opentofu:latest init + docker run --rm --network=host -v $(pwd)/integration_tests/terraform/keycloak:/tofu --workdir /tofu ghcr.io/opentofu/opentofu:latest plan + docker run --rm --network=host -v $(pwd)/integration_tests/terraform/keycloak:/tofu --workdir /tofu ghcr.io/opentofu/opentofu:latest apply -auto-approve + dbschema: docker run --rm -v $(pwd)/integration_tests/storage:/opt/storage --network=host --entrypoint /opt/storage/prepare-scylla.sh scylladb/scylla:5.0.1 \ No newline at end of file diff --git a/src/SnD.ApiClient/Boxer/BoxerTokenProvider.cs b/src/SnD.ApiClient/Boxer/BoxerTokenProvider.cs index 711d83e..2b646e6 100644 --- a/src/SnD.ApiClient/Boxer/BoxerTokenProvider.cs +++ b/src/SnD.ApiClient/Boxer/BoxerTokenProvider.cs @@ -11,7 +11,7 @@ public class BoxerTokenProvider : IJwtTokenExchangeProvider { private readonly Uri authProvider; private readonly Uri baseUri; - private string token; + private string? token; private readonly HttpClient httpClient; private readonly ILogger logger; private readonly Func> getExternalTokenAsync; diff --git a/test/SnD.ApiClient.Tests.Acceptance/Nexus/NexusAcceptancetests.cs b/test/SnD.ApiClient.Tests.Acceptance/Nexus/NexusAcceptancetests.cs index 3b977c6..6f2bcf2 100644 --- a/test/SnD.ApiClient.Tests.Acceptance/Nexus/NexusAcceptancetests.cs +++ b/test/SnD.ApiClient.Tests.Acceptance/Nexus/NexusAcceptancetests.cs @@ -35,8 +35,7 @@ public NexusAcceptanceTests() .AddSingleton() .AddLogging(conf => conf.AddConsole()) .Configure(configurationRoot.GetSection(nameof(NexusClientOptions))) - .AddNexusRetryPolicy(sp => new RetryAllErrors(sp.GetRequiredService>(), - sp.GetRequiredService>())) + .AddNexusRetryPolicy(sp => new RetryAllErrors(sp.GetRequiredService>(), sp.GetRequiredService>())) .AddNexusClient(); serviceCollection = configuration.UseBoxerTokenProviderOnAzure