diff --git a/tests/templates/kuttl/opensearch-dashboards/00-patch-ns.yaml b/tests/templates/kuttl/opensearch-dashboards/00-patch-ns.yaml new file mode 100644 index 0000000..d4f91fa --- /dev/null +++ b/tests/templates/kuttl/opensearch-dashboards/00-patch-ns.yaml @@ -0,0 +1,15 @@ +# see https://github.com/stackabletech/issues/issues/566 +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + kubectl patch namespace $NAMESPACE --patch=' + { + "metadata": { + "labels": { + "pod-security.kubernetes.io/enforce": "privileged" + } + } + }' + timeout: 120 diff --git a/tests/templates/kuttl/opensearch-dashboards/01-rbac.yaml b/tests/templates/kuttl/opensearch-dashboards/01-rbac.yaml new file mode 100644 index 0000000..64eced8 --- /dev/null +++ b/tests/templates/kuttl/opensearch-dashboards/01-rbac.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: test-service-account +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: test-role +rules: + - apiGroups: + - security.openshift.io + resources: + - securitycontextconstraints + resourceNames: + - privileged + verbs: + - use +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: test-role-binding +subjects: + - kind: ServiceAccount + name: test-service-account +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: test-role diff --git a/tests/templates/kuttl/opensearch-dashboards/02-assert.yaml.j2 b/tests/templates/kuttl/opensearch-dashboards/02-assert.yaml.j2 new file mode 100644 index 0000000..50b1d4c --- /dev/null +++ b/tests/templates/kuttl/opensearch-dashboards/02-assert.yaml.j2 @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +{% if lookup('env', 'VECTOR_AGGREGATOR') %} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vector-aggregator-discovery +{% endif %} diff --git a/tests/templates/kuttl/opensearch-dashboards/02-install-vector-aggregator-discovery-config-map.yaml.j2 b/tests/templates/kuttl/opensearch-dashboards/02-install-vector-aggregator-discovery-config-map.yaml.j2 new file mode 100644 index 0000000..2d6a0df --- /dev/null +++ b/tests/templates/kuttl/opensearch-dashboards/02-install-vector-aggregator-discovery-config-map.yaml.j2 @@ -0,0 +1,9 @@ +{% if lookup('env', 'VECTOR_AGGREGATOR') %} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vector-aggregator-discovery +data: + ADDRESS: {{ lookup('env', 'VECTOR_AGGREGATOR') }} +{% endif %} diff --git a/tests/templates/kuttl/opensearch-dashboards/10-assert.yaml.j2 b/tests/templates/kuttl/opensearch-dashboards/10-assert.yaml.j2 new file mode 100644 index 0000000..06c64a1 --- /dev/null +++ b/tests/templates/kuttl/opensearch-dashboards/10-assert.yaml.j2 @@ -0,0 +1,12 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: opensearch-nodes-default +status: + readyReplicas: 1 + replicas: 1 diff --git a/tests/templates/kuttl/opensearch-dashboards/10-install-opensearch.yaml.j2 b/tests/templates/kuttl/opensearch-dashboards/10-install-opensearch.yaml.j2 new file mode 100644 index 0000000..5d0871a --- /dev/null +++ b/tests/templates/kuttl/opensearch-dashboards/10-install-opensearch.yaml.j2 @@ -0,0 +1,193 @@ +--- +apiVersion: opensearch.stackable.tech/v1alpha1 +kind: OpenSearchCluster +metadata: + name: opensearch +spec: + image: +{% if test_scenario['values']['opensearch'].find(",") > 0 %} + custom: "{{ test_scenario['values']['opensearch'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['opensearch'].split(',')[0] }}" +{% else %} + productVersion: "{{ test_scenario['values']['opensearch'] }}" +{% endif %} + pullPolicy: IfNotPresent +{% if lookup('env', 'VECTOR_AGGREGATOR') %} + clusterConfig: + vectorAggregatorConfigMapName: vector-aggregator-discovery +{% endif %} + nodes: + config: + logging: + enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} + roleGroups: + default: + config: + listenerClass: external-stable + replicas: 1 + podOverrides: + spec: + volumes: + - name: tls + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + secrets.stackable.tech/scope: node,pod,service=opensearch,service=opensearch-nodes-default-headless + envOverrides: + # Only required for the official image + # The official image (built with https://github.com/opensearch-project/opensearch-build) + # installs a demo configuration if not disabled explicitly. + DISABLE_INSTALL_DEMO_CONFIG: "true" + OPENSEARCH_HOME: {{ test_scenario['values']['opensearch_home'] }} + configOverrides: + opensearch.yml: + # Disable memory mapping in this test; If memory mapping were activated, the kernel setting + # vm.max_map_count would have to be increased to 262144 on the node. + node.store.allow_mmap: "false" + # Disable the disk allocation decider in this test; Otherwise the test depends on the disk + # usage of the node and if the relative watermark set in + # `cluster.routing.allocation.disk.watermark.high` is reached then the security index could + # not be created even if enough disk space would be available. + cluster.routing.allocation.disk.threshold_enabled: "false" + plugins.security.allow_default_init_securityindex: "true" + plugins.security.ssl.transport.enabled: "true" + plugins.security.ssl.transport.pemcert_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.crt + plugins.security.ssl.transport.pemkey_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.key + plugins.security.ssl.transport.pemtrustedcas_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt + plugins.security.ssl.http.enabled: "true" + plugins.security.ssl.http.pemcert_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.crt + plugins.security.ssl.http.pemkey_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.key + plugins.security.ssl.http.pemtrustedcas_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt + podOverrides: + spec: + containers: + - name: opensearch + volumeMounts: + - name: security-config + mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/opensearch-security + readOnly: true + - name: tls + mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/tls + readOnly: true + volumes: + - name: security-config + secret: + secretName: opensearch-security-config + - name: tls + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + secrets.stackable.tech/class: tls + spec: + storageClassName: secrets.stackable.tech + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "1" +--- +apiVersion: v1 +kind: Secret +metadata: + name: opensearch-credentials +data: + admin: QUpWRnNHSkJicFQ2bUNobg== # AJVFsGJBbpT6mChn + kibanaserver: RTRrRU51RW1rcUgzanlIQw== # E4kENuEmkqH3jyHC +--- +apiVersion: v1 +kind: Secret +metadata: + name: opensearch-security-config +stringData: + action_groups.yml: | + --- + _meta: + type: actiongroups + config_version: 2 + allowlist.yml: | + --- + _meta: + type: allowlist + config_version: 2 + + config: + enabled: false + audit.yml: | + --- + _meta: + type: audit + config_version: 2 + + config: + enabled: false + config.yml: | + --- + _meta: + type: config + config_version: 2 + + config: + dynamic: + authc: + basic_internal_auth_domain: + description: Authenticate via HTTP Basic against internal users database + http_enabled: true + transport_enabled: true + order: 1 + http_authenticator: + type: basic + challenge: true + authentication_backend: + type: intern + authz: {} + internal_users.yml: | + --- + # The hash value is a bcrypt hash and can be generated with plugin/tools/hash.sh + + _meta: + type: internalusers + config_version: 2 + + admin: + hash: $2y$10$xRtHZFJ9QhG9GcYhRpAGpufCZYsk//nxsuel5URh0GWEBgmiI4Q/e + reserved: true + backend_roles: + - admin + description: OpenSearch admin user + + kibanaserver: + hash: $2y$10$vPgQ/6ilKDM5utawBqxoR.7euhVQ0qeGl8mPTeKhmFT475WUDrfQS + reserved: true + description: OpenSearch Dashboards user + nodes_dn.yml: | + --- + _meta: + type: nodesdn + config_version: 2 + roles.yml: | + --- + _meta: + type: roles + config_version: 2 + roles_mapping.yml: | + --- + _meta: + type: rolesmapping + config_version: 2 + + all_access: + reserved: false + backend_roles: + - admin + + kibana_server: + reserved: true + users: + - kibanaserver + tenants.yml: | + --- + _meta: + type: tenants + config_version: 2 diff --git a/tests/templates/kuttl/opensearch-dashboards/20-assert.yaml b/tests/templates/kuttl/opensearch-dashboards/20-assert.yaml new file mode 100644 index 0000000..becb010 --- /dev/null +++ b/tests/templates/kuttl/opensearch-dashboards/20-assert.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: opensearch-dashboards +status: + readyReplicas: 1 + replicas: 1 diff --git a/tests/templates/kuttl/opensearch-dashboards/20-install-opensearch-dashboards.yaml.j2 b/tests/templates/kuttl/opensearch-dashboards/20-install-opensearch-dashboards.yaml.j2 new file mode 100644 index 0000000..14dc539 --- /dev/null +++ b/tests/templates/kuttl/opensearch-dashboards/20-install-opensearch-dashboards.yaml.j2 @@ -0,0 +1,13 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: > + helm install opensearch-dashboards opensearch-dashboards + --repo https://opensearch-project.github.io/helm-charts + --version "{{ test_scenario['values']['opensearch'].split(',')[0] }}" + --values 20_opensearch-dashboards-values.yaml + --set opensearchHosts=https://opensearch.$NAMESPACE.svc.cluster.local:9200 + --namespace $NAMESPACE + --wait + timeout: 600 diff --git a/tests/templates/kuttl/opensearch-dashboards/20_opensearch-dashboards-values.yaml.j2 b/tests/templates/kuttl/opensearch-dashboards/20_opensearch-dashboards-values.yaml.j2 new file mode 100644 index 0000000..d1caea0 --- /dev/null +++ b/tests/templates/kuttl/opensearch-dashboards/20_opensearch-dashboards-values.yaml.j2 @@ -0,0 +1,55 @@ +image: + repository: oci.stackable.tech/sdp/opensearch-dashboards + tag: {{ test_scenario['values']['opensearch'].split(',')[0] }}-stackable0.0.0-dev +service: + type: NodePort +serviceAccount: + create: false + # Use the ServiceAccount of OpenSearch because its permissions are already configured to work on + # OpenShift. + name: opensearch-serviceaccount +extraEnvs: + - name: OPENSEARCH_PASSWORD + valueFrom: + secretKeyRef: + name: opensearch-credentials + key: kibanaserver +config: + opensearch_dashboards.yml: + opensearch: + username: kibanaserver + requestHeadersWhitelist: + - authorization + - securitytenant + ssl: + verificationMode: full + certificateAuthorities: [/stackable/opensearch-dashboards/config/tls/ca.crt] + opensearch_security: + multitenancy: + enabled: true + tenants.preferred: [Private, Global] + readonly_mode.roles: [kibana_read_only] + cookie.secure: false +extraVolumes: + - name: tls + ephemeral: + volumeClaimTemplate: + metadata: + annotations: + secrets.stackable.tech/class: tls + spec: + storageClassName: secrets.stackable.tech + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "1" +extraVolumeMounts: + - name: tls + mountPath: /stackable/opensearch-dashboards/config/tls + # The Helm chart only adds a volume mount at /usr/share/opensearch-dashboards + - mountPath: /stackable/opensearch-dashboards/config/opensearch_dashboards.yml + name: config + subPath: opensearch_dashboards.yml +podSecurityContext: + fsGroup: 1000 diff --git a/tests/templates/kuttl/opensearch-dashboards/30-assert.yaml b/tests/templates/kuttl/opensearch-dashboards/30-assert.yaml new file mode 100644 index 0000000..0b78e78 --- /dev/null +++ b/tests/templates/kuttl/opensearch-dashboards/30-assert.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: test-opensearch-dashboards +status: + succeeded: 1 diff --git a/tests/templates/kuttl/opensearch-dashboards/30-test-opensearch-dashboards.yaml.j2 b/tests/templates/kuttl/opensearch-dashboards/30-test-opensearch-dashboards.yaml.j2 new file mode 100644 index 0000000..0ced664 --- /dev/null +++ b/tests/templates/kuttl/opensearch-dashboards/30-test-opensearch-dashboards.yaml.j2 @@ -0,0 +1,142 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: test-opensearch-dashboards +spec: + template: + spec: + containers: + - name: test-opensearch-dashboards + image: oci.stackable.tech/sdp/testing-tools:0.2.0-stackable0.0.0-dev + command: + - python + args: + - scripts/test.py + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: OPENSEARCH_USER + value: admin + - name: OPENSEARCH_PASSWORD + valueFrom: + secretKeyRef: + name: opensearch-credentials + key: admin + volumeMounts: + - name: script + mountPath: /stackable/scripts + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsNonRoot: true + resources: + requests: + memory: 128Mi + cpu: 100m + limits: + memory: 128Mi + cpu: 400m + volumes: + - name: script + configMap: + name: test-opensearch-dashboards + serviceAccountName: test-service-account + securityContext: + fsGroup: 1000 + restartPolicy: OnFailure +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-opensearch-dashboards +data: + test.py: | + import logging + import os + import requests + import sys + + logging.basicConfig( + level="DEBUG", format="%(asctime)s %(levelname)s: %(message)s", stream=sys.stdout + ) + + namespace = os.environ["NAMESPACE"] + opensearch_user = os.environ["OPENSEARCH_USER"] + opensearch_password = os.environ["OPENSEARCH_PASSWORD"] + opensearch_dashboards_service = "http://opensearch-dashboards:5601" + + session = requests.Session() + session.headers.update({"osd-xsrf": "true"}) + + login_page = session.post( + f"{opensearch_dashboards_service}/auth/login", + data={"username": opensearch_user, "password": opensearch_password}, + ) + assert login_page.ok, "Failed to login to OpenSearch Dashboards" + + api_status = session.get(f"{opensearch_dashboards_service}/api/status") + assert api_status.ok, "Failed to get API status" + + opensearch_version = api_status.json()["version"]["number"] + + assert api_status.json()["status"]["overall"]["state"] == "green", ( + "Overall state of OpenSearch Dashboards is not green" + ) + + # Check if all expected plugins are present and working + expected_plugins = [ + "alertingDashboards", + "anomalyDetectionDashboards", + "assistantDashboards", + "customImportMapDashboards", + "flowFrameworkDashboards", + "indexManagementDashboards", + "mlCommonsDashboards", + "notificationsDashboards", + "observabilityDashboards", + "queryInsightsDashboards", + "queryWorkbenchDashboards", + "reportsDashboards", + "searchRelevanceDashboards", + "securityAnalyticsDashboards", + "securityDashboards", + ] + + states = {} + for status in api_status.json()["status"]["statuses"]: + states[ + status["id"].removeprefix("plugin:").removesuffix(f"@{opensearch_version}") + ] = status["state"] == "green" + for plugin in expected_plugins: + assert plugin in states and states[plugin], ( + f"Expected plugin {plugin} not present or working." + ) + + # Load Sample Data (web logs & flights) + sample_web_logs = session.post(f"{opensearch_dashboards_service}/api/sample_data/logs") + assert sample_web_logs.ok, "Failed to create sample data (logs)" + + sample_flights = session.post( + f"{opensearch_dashboards_service}/api/sample_data/flights" + ) + assert sample_flights.ok, "Failed to create sample data (flights)" + + # Check that the indices were created + indices = session.get( + f"{opensearch_dashboards_service}/api/saved_objects/_find?fields=title&per_page=10000&type=index-pattern" + ) + assert indices.ok, "Failed to get indices" + + logs_index = indices.json()["saved_objects"][0] + assert logs_index["attributes"]["title"] == "opensearch_dashboards_sample_data_logs", ( + "First index should be sample logs" + ) + + flights_index = indices.json()["saved_objects"][1] + assert ( + flights_index["attributes"]["title"] == "opensearch_dashboards_sample_data_flights" + ), "Second index should be sample flights" diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml index 6e18a83..de7d2b3 100644 --- a/tests/test-definition.yaml +++ b/tests/test-definition.yaml @@ -30,6 +30,10 @@ tests: - name: logging dimensions: - opensearch + - name: opensearch-dashboards + dimensions: + - opensearch + - opensearch_home suites: - name: nightly patch: @@ -39,6 +43,7 @@ suites: - name: smoke-latest select: - smoke + - opensearch-dashboards patch: - dimensions: - expr: last @@ -54,6 +59,7 @@ suites: - smoke - external-access - ldap + - opensearch-dashboards patch: - dimensions: - name: opensearch