├── .prow_ci.env ├── test ├── kuttl │ ├── tests │ │ ├── keystone_tls │ │ │ ├── 00-assert.yaml │ │ │ ├── 00-tls_ca_bundle.yaml │ │ │ ├── 02-cleanup-keystone.yaml │ │ │ ├── 02-errors.yaml │ │ │ ├── 01-keystoneapi_tls_deploy.yaml │ │ │ ├── 00-tls_cert_keystone-public-svc.yaml │ │ │ ├── 00-tls_cert_keystone-internal-svc.yaml │ │ │ └── 01-assert.yaml │ │ ├── fernet_rotation │ │ │ ├── 05-errors.yaml │ │ │ ├── 00-assert.yaml │ │ │ ├── 00-deploy_keystone.yaml │ │ │ ├── 05-cleanup-keystone.yaml │ │ │ ├── 03-rotate_keys_until_invalidate.yaml │ │ │ ├── 04-cleanup-openstackclient.yaml │ │ │ ├── 02-rotate_keys.yaml │ │ │ ├── 01-assert.yaml │ │ │ ├── 02-assert.yaml │ │ │ ├── 03-assert.yaml │ │ │ └── 01-deploy_openstackclient.yaml │ │ ├── keystone_scale │ │ │ ├── 00-assert.yaml │ │ │ ├── 04-errors.yaml │ │ │ ├── 00-deploy_keystone.yaml │ │ │ ├── 04-cleanup-keystone.yaml │ │ │ ├── 03-errors.yaml │ │ │ ├── 01-scale-keystoneapi.yaml │ │ │ ├── 02-scale-down-keystoneapi.yaml │ │ │ ├── 03-scale-down-zero-keystoneapi.yaml │ │ │ ├── 02-assert.yaml │ │ │ ├── 01-assert.yaml │ │ │ └── 03-assert.yaml │ │ ├── keystone_resources │ │ │ ├── 00-assert.yaml │ │ │ ├── 05-errors.yaml │ │ │ ├── 00-deploy_keystone.yaml │ │ │ ├── 05-cleanup-keystone.yaml │ │ │ ├── 01-deploy-keystoneservice.yaml │ │ │ ├── 02-deploy-keystoneendpoint.yaml │ │ │ ├── 03-cleanup-keystoneendpoint.yaml │ │ │ ├── 04-cleanup-keystoneservice.yaml │ │ │ ├── 03-errors.yaml │ │ │ ├── 04-errors.yaml │ │ │ ├── 04-assert.yaml │ │ │ ├── 03-assert.yaml │ │ │ ├── 01-assert.yaml │ │ │ └── 02-assert.yaml │ │ └── change_keystone_config │ │ │ ├── 00-assert.yaml │ │ │ ├── 03-cleanup-keystone.yaml │ │ │ ├── 03-errors.yaml │ │ │ ├── 00-deploy_keystone.yaml │ │ │ ├── 02-assert.yaml │ │ │ ├── 01-assert.yaml │ │ │ └── 02-change_keystone_config.yaml │ └── common │ │ ├── keystoneapi_deploy.yaml │ │ ├── keystoneapi_tls_deploy.yaml │ │ ├── cleanup-keystone.yaml │ │ ├── scripts │ │ ├── create_test_token.sh │ │ ├── test_invalid_token.sh │ │ ├── check_debug_in_keystone_pod_logs.sh │ │ ├── rotate_token.sh │ │ └── validate_test_token.sh │ │ ├── patch_keystone_deploy.yaml │ │ ├── assert_tls_cert.yaml │ │ ├── errors_cleanup_keystone.yaml │ │ ├── tls_ca_bundle.yaml │ │ ├── tls_cert_keystone-public-svc.yaml │ │ ├── tls_cert_keystone-internal-svc.yaml │ │ └── assert_sample_deployment.yaml └── functional │ └── base_test.go ├── config ├── network-policy │ ├── kustomization.yaml │ ├── allow-metrics-traffic.yaml │ └── allow-webhook-traffic.yaml ├── webhook │ ├── kustomization.yaml │ ├── service.yaml │ ├── kustomizeconfig.yaml │ └── manifests.yaml ├── certmanager │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── issuer.yaml │ ├── certificate-webhook.yaml │ └── certificate-metrics.yaml ├── scorecard │ ├── bases │ │ └── config.yaml │ ├── patches │ │ ├── basic.config.yaml │ │ └── olm.config.yaml │ └── kustomization.yaml ├── default │ ├── manager_metrics_patch.yaml │ ├── metrics_service.yaml │ ├── cert_metrics_manager_patch.yaml │ └── manager_webhook_patch.yaml ├── rbac │ ├── metrics_reader_role.yaml │ ├── service_account.yaml │ ├── metrics_auth_role_binding.yaml │ ├── metrics_auth_role.yaml │ ├── role_binding.yaml │ ├── leader_election_role_binding.yaml │ ├── leader_election_role.yaml │ ├── keystoneapi_admin_role.yaml │ ├── keystoneendpoint_admin_role.yaml │ ├── keystoneservice_admin_role.yaml │ ├── keystoneapi_viewer_role.yaml │ ├── keystoneservice_viewer_role.yaml │ ├── keystoneendpoint_viewer_role.yaml │ ├── keystoneapi_editor_role.yaml │ ├── keystoneservice_editor_role.yaml │ ├── keystoneendpoint_editor_role.yaml │ ├── kustomization.yaml │ └── role.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── samples │ ├── kustomization.yaml │ ├── keystone_v1beta1_keystoneservice.yaml │ ├── keystone_v1beta1_keystoneendpoint.yaml │ ├── keystone_v1beta1_keystoneapi.yaml │ └── keystone_v1beta1_keystoneapi_tls.yaml ├── prometheus │ ├── kustomization.yaml │ ├── monitor_tls_patch.yaml │ └── monitor.yaml ├── crd │ ├── kustomizeconfig.yaml │ ├── kustomization.yaml │ └── bases │ │ └── keystone.openstack.org_keystoneservices.yaml └── manifests │ ├── kustomization.yaml │ └── bases │ └── keystone-operator.clusterserviceversion.yaml ├── .ci-operator.yaml ├── .dockerignore ├── hack ├── clean_local_webhook.sh ├── build-crd-schema-checker.sh ├── boilerplate.go.txt ├── crd-schema-checker.sh └── run_with_local_webhook.sh ├── OWNERS ├── .github └── workflows │ ├── release-branch-sync.yaml │ ├── lints.yaml │ ├── force-bump-pr-manual.yaml │ ├── force-bump-pr-scheduled.yaml │ ├── build-keystone-operator.yaml │ └── release-keystone-operator.yaml ├── .golangci.yaml ├── OWNERS_ALIASES ├── zuul.d ├── projects.yaml └── jobs.yaml ├── kuttl-test.yaml ├── .gitignore ├── renovate.json ├── templates └── keystoneapi │ └── config │ ├── ssl.conf │ ├── keystone.conf │ ├── httpd.conf │ └── keystone-api-config.json ├── internal ├── keystone │ ├── fernet.go │ ├── funcs.go │ ├── federation.go │ ├── cloudconfig.go │ ├── const.go │ ├── dbsync.go │ ├── cronjob.go │ ├── volumes.go │ └── bootstrap.go └── webhook │ └── v1beta1 │ └── keystoneapi_webhook.go ├── api ├── v1beta1 │ ├── groupversion_info.go │ ├── keystoneservice_types.go │ ├── keystoneendpoint_types.go │ ├── conditions.go │ └── keystoneservice.go ├── go.mod └── bases │ ├── keystone.openstack.org_keystoneservices.yaml │ └── keystone.openstack.org_keystoneendpoints.yaml ├── PROJECT ├── .pull_request_pipeline ├── .pre-commit-config.yaml ├── Dockerfile └── README.md /.prow_ci.env: -------------------------------------------------------------------------------- 1 | export USE_IMAGE_DIGESTS=true 2 | export FAIL_FIPS_CHECK=true 3 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_tls/00-assert.yaml: -------------------------------------------------------------------------------- 1 | ../../common/assert_tls_cert.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_tls/00-tls_ca_bundle.yaml: -------------------------------------------------------------------------------- 1 | ../../common/tls_ca_bundle.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/fernet_rotation/05-errors.yaml: -------------------------------------------------------------------------------- 1 | ../../common/errors_cleanup_keystone.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_scale/00-assert.yaml: -------------------------------------------------------------------------------- 1 | ../../common/assert_sample_deployment.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_scale/04-errors.yaml: -------------------------------------------------------------------------------- 1 | ../../common/errors_cleanup_keystone.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_tls/02-cleanup-keystone.yaml: -------------------------------------------------------------------------------- 1 | ../../common/cleanup-keystone.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_tls/02-errors.yaml: -------------------------------------------------------------------------------- 1 | ../../common/errors_cleanup_keystone.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/fernet_rotation/00-assert.yaml: -------------------------------------------------------------------------------- 1 | ../../common/assert_sample_deployment.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/fernet_rotation/00-deploy_keystone.yaml: -------------------------------------------------------------------------------- 1 | ../../common/keystoneapi_deploy.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/fernet_rotation/05-cleanup-keystone.yaml: -------------------------------------------------------------------------------- 1 | ../../common/cleanup-keystone.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_resources/00-assert.yaml: -------------------------------------------------------------------------------- 1 | ../../common/assert_sample_deployment.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_resources/05-errors.yaml: -------------------------------------------------------------------------------- 1 | ../../common/errors_cleanup_keystone.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_scale/00-deploy_keystone.yaml: -------------------------------------------------------------------------------- 1 | ../../common/keystoneapi_deploy.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_scale/04-cleanup-keystone.yaml: -------------------------------------------------------------------------------- 1 | ../../common/cleanup-keystone.yaml -------------------------------------------------------------------------------- /test/kuttl/common/keystoneapi_deploy.yaml: -------------------------------------------------------------------------------- 1 | ../../../config/samples/keystone_v1beta1_keystoneapi.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/change_keystone_config/00-assert.yaml: -------------------------------------------------------------------------------- 1 | ../../common/assert_sample_deployment.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/change_keystone_config/03-cleanup-keystone.yaml: -------------------------------------------------------------------------------- 1 | ../../common/cleanup-keystone.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/change_keystone_config/03-errors.yaml: -------------------------------------------------------------------------------- 1 | ../../common/errors_cleanup_keystone.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_resources/00-deploy_keystone.yaml: -------------------------------------------------------------------------------- 1 | ../../common/keystoneapi_deploy.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_resources/05-cleanup-keystone.yaml: -------------------------------------------------------------------------------- 1 | ../../common/cleanup-keystone.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/change_keystone_config/00-deploy_keystone.yaml: -------------------------------------------------------------------------------- 1 | ../../common/keystoneapi_deploy.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_tls/01-keystoneapi_tls_deploy.yaml: -------------------------------------------------------------------------------- 1 | ../../common/keystoneapi_tls_deploy.yaml -------------------------------------------------------------------------------- /test/kuttl/common/keystoneapi_tls_deploy.yaml: -------------------------------------------------------------------------------- 1 | ../../../config/samples/keystone_v1beta1_keystoneapi_tls.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_tls/00-tls_cert_keystone-public-svc.yaml: -------------------------------------------------------------------------------- 1 | ../../common/tls_cert_keystone-public-svc.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_tls/00-tls_cert_keystone-internal-svc.yaml: -------------------------------------------------------------------------------- 1 | ../../common/tls_cert_keystone-internal-svc.yaml -------------------------------------------------------------------------------- /config/network-policy/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - allow-webhook-traffic.yaml 3 | - allow-metrics-traffic.yaml 4 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_resources/01-deploy-keystoneservice.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/samples/keystone_v1beta1_keystoneservice.yaml -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_resources/02-deploy-keystoneendpoint.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/samples/keystone_v1beta1_keystoneendpoint.yaml -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /.ci-operator.yaml: -------------------------------------------------------------------------------- 1 | build_root_image: 2 | name: tools 3 | namespace: openstack-k8s-operators 4 | tag: ci-build-root-golang-1.24-sdk-1.41.1 5 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - issuer.yaml 3 | - certificate-webhook.yaml 4 | - certificate-metrics.yaml 5 | 6 | configurations: 7 | - kustomizeconfig.yaml 8 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_scale/03-errors.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | annotations: 5 | openshift.io/scc: anyuid 6 | labels: 7 | service: keystone 8 | -------------------------------------------------------------------------------- /test/kuttl/tests/fernet_rotation/03-rotate_keys_until_invalidate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | commands: 4 | - script: ../../common/scripts/rotate_token.sh 5 | -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /test/kuttl/common/cleanup-keystone.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | delete: 4 | - apiVersion: keystone.openstack.org/v1beta1 5 | kind: KeystoneAPI 6 | name: keystone 7 | -------------------------------------------------------------------------------- /test/kuttl/tests/fernet_rotation/04-cleanup-openstackclient.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | delete: 4 | - apiVersion: v1 5 | kind: Pod 6 | name: openstackclient 7 | -------------------------------------------------------------------------------- /test/kuttl/tests/fernet_rotation/02-rotate_keys.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: keystone 5 | annotations: 6 | keystone.openstack.org/rotatedat: "2009-11-10T23:00:00Z" 7 | -------------------------------------------------------------------------------- /test/kuttl/tests/fernet_rotation/01-assert.yaml: -------------------------------------------------------------------------------- 1 | # create a test token, save it in /tmp/ 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | commands: 5 | - script: ../../common/scripts/create_test_token.sh 6 | -------------------------------------------------------------------------------- /hack/clean_local_webhook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | oc delete validatingwebhookconfiguration/vkeystoneapi.kb.io --ignore-not-found 5 | oc delete mutatingwebhookconfiguration/mkeystoneapi.kb.io --ignore-not-found 6 | -------------------------------------------------------------------------------- /config/default/manager_metrics_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch adds the args to allow exposing the metrics endpoint using HTTPS 2 | - op: add 3 | path: /spec/template/spec/containers/0/args/0 4 | value: --metrics-bind-address=:8443 5 | -------------------------------------------------------------------------------- /config/rbac/metrics_reader_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: 7 | - "/metrics" 8 | verbs: 9 | - get 10 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_resources/03-cleanup-keystoneendpoint.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | delete: 4 | - apiVersion: keystone.openstack.org/v1beta1 5 | kind: KeystoneEndpoint 6 | name: placement 7 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_resources/04-cleanup-keystoneservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | delete: 4 | - apiVersion: keystone.openstack.org/v1beta1 5 | kind: KeystoneService 6 | name: placement 7 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_resources/03-errors.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # - No KeystoneEndpoint CR 5 | # 6 | apiVersion: keystone.openstack.org/v1beta1 7 | kind: KeystoneEndpoint 8 | metadata: 9 | name: placement 10 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_resources/04-errors.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # - No KeystoneService CR 5 | # 6 | apiVersion: keystone.openstack.org/v1beta1 7 | kind: KeystoneService 8 | metadata: 9 | name: placement 10 | -------------------------------------------------------------------------------- /test/kuttl/tests/change_keystone_config/02-assert.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestAssert 3 | namespaced: true 4 | commands: 5 | - script: $KEYSTONE_KUTTL_DIR/../common/scripts/check_debug_in_keystone_pod_logs.sh --reverse 6 | -------------------------------------------------------------------------------- /test/kuttl/tests/fernet_rotation/02-assert.yaml: -------------------------------------------------------------------------------- 1 | # validate that token saved in previous step works 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | namespaced: true 5 | commands: 6 | - script: ../../common/scripts/validate_test_token.sh 7 | -------------------------------------------------------------------------------- /test/kuttl/tests/fernet_rotation/03-assert.yaml: -------------------------------------------------------------------------------- 1 | # validate that token saved in previous step works 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | namespaced: true 5 | commands: 6 | - script: ../../common/scripts/test_invalid_token.sh 7 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners 2 | approvers: 3 | - ci-approvers 4 | - openstack-approvers 5 | - security-approvers 6 | 7 | reviewers: 8 | - ci-approvers 9 | - openstack-approvers 10 | - security-approvers 11 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: keystone-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: controller-manager 8 | namespace: system 9 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: quay.io/openstack-k8s-operators/keystone-operator 8 | newTag: latest 9 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_scale/01-scale-keystoneapi.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | commands: 4 | - script: | 5 | oc patch keystoneapi -n $NAMESPACE keystone --type='json' -p='[{"op": "replace", "path": "/spec/replicas", "value":3}]' 6 | -------------------------------------------------------------------------------- /test/kuttl/tests/change_keystone_config/01-assert.yaml: -------------------------------------------------------------------------------- 1 | # check that by default, debug is not set in keystone config 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestAssert 4 | commands: 5 | - script: $KEYSTONE_KUTTL_DIR/../common/scripts/check_debug_in_keystone_pod_logs.sh 6 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_resources/04-assert.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # - 1 KeystoneAPI CR 5 | # 6 | apiVersion: keystone.openstack.org/v1beta1 7 | kind: KeystoneAPI 8 | metadata: 9 | finalizers: 10 | - openstack.org/keystoneapi 11 | name: keystone 12 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_scale/02-scale-down-keystoneapi.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | commands: 4 | - script: | 5 | oc patch keystoneapi -n $NAMESPACE keystone --type='json' -p='[{"op": "replace", "path": "/spec/replicas", "value":1}]' 6 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_scale/03-scale-down-zero-keystoneapi.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | commands: 4 | - script: | 5 | oc patch keystoneapi -n $NAMESPACE keystone --type='json' -p='[{"op": "replace", "path": "/spec/replicas", "value":0}]' 6 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - keystone_v1beta1_keystoneapi.yaml 4 | - keystone_v1beta1_keystoneservice.yaml 5 | - keystone_v1beta1_keystoneendpoint.yaml 6 | #+kubebuilder:scaffold:manifestskustomizesamples 7 | -------------------------------------------------------------------------------- /test/kuttl/tests/change_keystone_config/02-change_keystone_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | commands: 4 | - script: | 5 | oc patch keystoneapi -n $NAMESPACE keystone --type='json' -p='[{"op": "replace", "path": "/spec/customServiceConfig", "value": "[DEFAULT]\ndebug = false"}]' 6 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.41.1 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /.github/workflows/release-branch-sync.yaml: -------------------------------------------------------------------------------- 1 | name: Release Branch sync 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | schedule: 8 | - cron: '0 * * * *' 9 | 10 | jobs: 11 | call-build-workflow: 12 | uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/release-branch-sync.yaml@main 13 | -------------------------------------------------------------------------------- /test/kuttl/common/scripts/create_test_token.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -euxo pipefail 3 | 4 | oc wait --for=condition=ready pod openstackclient --timeout=30s -n $NAMESPACE 5 | 6 | alias openstack="oc exec -tn $NAMESPACE openstackclient -- openstack" 7 | 8 | export OS_TOKEN=$(openstack token issue -f value -c id) 9 | 10 | echo $OS_TOKEN > /tmp/temporary_test_token 11 | -------------------------------------------------------------------------------- /config/rbac/metrics_auth_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: metrics-auth-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: metrics-auth-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | linters: 4 | # Enable specific linter 5 | # https://golangci-lint.run/usage/linters/#enabled-by-default 6 | enable: 7 | - errorlint 8 | - revive 9 | - ginkgolinter 10 | - govet 11 | - gosec 12 | - errname 13 | - err113 14 | 15 | formatters: 16 | enable: 17 | - gofmt 18 | 19 | run: 20 | timeout: 5m 21 | -------------------------------------------------------------------------------- /config/rbac/metrics_auth_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-auth-role 5 | rules: 6 | - apiGroups: 7 | - authentication.k8s.io 8 | resources: 9 | - tokenreviews 10 | verbs: 11 | - create 12 | - apiGroups: 13 | - authorization.k8s.io 14 | resources: 15 | - subjectaccessreviews 16 | verbs: 17 | - create 18 | -------------------------------------------------------------------------------- /config/samples/keystone_v1beta1_keystoneservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: keystone.openstack.org/v1beta1 2 | kind: KeystoneService 3 | metadata: 4 | name: placement 5 | spec: 6 | serviceUser: placement 7 | enabled: true 8 | serviceDescription: "Placement service" 9 | serviceName: placement 10 | serviceType: placement 11 | secret: osp-secret 12 | passwordSelector: PlacementPassword 13 | -------------------------------------------------------------------------------- /OWNERS_ALIASES: -------------------------------------------------------------------------------- 1 | # See the OWNERS_ALIASES docs: https://git.k8s.io/community/contributors/guide/owners.md#owners_aliases 2 | 3 | aliases: 4 | ci-approvers: 5 | - viroel 6 | openstack-approvers: 7 | - abays 8 | - dprince 9 | - olliewalsh 10 | - stuggi 11 | security-approvers: 12 | - afaranha 13 | - d34dh0r53 14 | - dmendiza 15 | - mauricioharley 16 | - vakwetu 17 | - xek 18 | -------------------------------------------------------------------------------- /test/kuttl/common/patch_keystone_deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kuttl.dev/v1beta1 2 | kind: TestStep 3 | commands: 4 | - script: | 5 | oc patch keystoneapi -n $NAMESPACE keystone --type='json' -p='[{"op": "replace", "path": "/spec/secret", "value":"osp-secret"}]' 6 | oc patch keystoneapi -n $NAMESPACE keystone --type='json' -p='[{"op": "replace", "path": "/spec/storageClass", "value":"local-storage"}]' 7 | -------------------------------------------------------------------------------- /test/kuttl/common/assert_tls_cert.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # - 2 tls cert secrets 5 | # - 1 tls ca bundle secrets 6 | 7 | apiVersion: v1 8 | kind: Secret 9 | metadata: 10 | name: cert-keystone-internal-svc 11 | --- 12 | apiVersion: v1 13 | kind: Secret 14 | metadata: 15 | name: cert-keystone-public-svc 16 | --- 17 | apiVersion: v1 18 | kind: Secret 19 | metadata: 20 | name: combined-ca-bundle 21 | -------------------------------------------------------------------------------- /config/samples/keystone_v1beta1_keystoneendpoint.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: keystone.openstack.org/v1beta1 2 | kind: KeystoneEndpoint 3 | metadata: 4 | name: placement 5 | spec: 6 | serviceName: placement 7 | endpoints: 8 | admin: http://placement-admin-openstack.apps-crc.testing 9 | internal: http://placement-internal-openstack.apps-crc.testing 10 | public: http://placement-public-openstack.apps-crc.testing 11 | -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: keystone-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: webhook-service 8 | namespace: system 9 | spec: 10 | ports: 11 | - port: 443 12 | protocol: TCP 13 | targetPort: 9443 14 | selector: 15 | control-plane: controller-manager 16 | app.kubernetes.io/name: keystone-operator 17 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: keystone-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: manager-rolebinding 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: ClusterRole 11 | name: manager-role 12 | subjects: 13 | - kind: ServiceAccount 14 | name: controller-manager 15 | namespace: system 16 | -------------------------------------------------------------------------------- /hack/build-crd-schema-checker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | 4 | if [ -f "$INSTALL_DIR/crd-schema-checker" ]; then 5 | exit 0 6 | fi 7 | 8 | mkdir -p "$INSTALL_DIR/git-tmp" 9 | git clone https://github.com/openshift/crd-schema-checker.git \ 10 | -b "$CRD_SCHEMA_CHECKER_VERSION" "$INSTALL_DIR/git-tmp" 11 | pushd "$INSTALL_DIR/git-tmp" 12 | GOWORK=off make 13 | cp crd-schema-checker "$INSTALL_DIR/" 14 | popd 15 | rm -rf "$INSTALL_DIR/git-tmp" 16 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: keystone-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: leader-election-rolebinding 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: Role 11 | name: leader-election-role 12 | subjects: 13 | - kind: ServiceAccount 14 | name: controller-manager 15 | namespace: system 16 | -------------------------------------------------------------------------------- /config/samples/keystone_v1beta1_keystoneapi.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: keystone.openstack.org/v1beta1 2 | kind: KeystoneAPI 3 | metadata: 4 | name: keystone 5 | spec: 6 | adminProject: admin 7 | adminUser: admin 8 | customServiceConfig: | 9 | [DEFAULT] 10 | debug = true 11 | databaseInstance: openstack 12 | databaseAccount: keystone 13 | preserveJobs: false 14 | region: regionOne 15 | secret: osp-secret 16 | resources: 17 | requests: 18 | memory: "500Mi" 19 | cpu: "1.0" 20 | -------------------------------------------------------------------------------- /.github/workflows/lints.yaml: -------------------------------------------------------------------------------- 1 | name: Lints 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | check-go-mod-replace-lines: 7 | name: check for replace lines in go.mod files 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout project code 11 | uses: actions/checkout@v3 12 | - name: check for replace lines in go.mod files 13 | run: | 14 | ! egrep --invert-match -e '^replace.*/api => \./api|^replace.*//allow-merging$' `find . -name 'go.mod'` | egrep -e 'go.mod:replace' 15 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_resources/03-assert.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # - 1 KeystoneAPI CR 5 | # - 1 KeystoneService CR 6 | # 7 | apiVersion: keystone.openstack.org/v1beta1 8 | kind: KeystoneAPI 9 | metadata: 10 | finalizers: 11 | - openstack.org/keystoneapi 12 | - openstack.org/keystoneservice-placement 13 | name: keystone 14 | --- 15 | apiVersion: keystone.openstack.org/v1beta1 16 | kind: KeystoneService 17 | metadata: 18 | finalizers: 19 | - openstack.org/keystoneservice 20 | name: placement 21 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_scale/02-assert.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # - 1 KeystoneAPI CR 5 | # - 1 Pods for KeystoneAPI CR 6 | # 7 | 8 | apiVersion: keystone.openstack.org/v1beta1 9 | kind: KeystoneAPI 10 | metadata: 11 | finalizers: 12 | - openstack.org/keystoneapi 13 | name: keystone 14 | spec: 15 | replicas: 1 16 | status: 17 | readyCount: 1 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: keystone 23 | spec: 24 | replicas: 1 25 | status: 26 | availableReplicas: 1 27 | -------------------------------------------------------------------------------- /config/default/metrics_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: keystone-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: controller-manager-metrics-service 9 | namespace: system 10 | spec: 11 | ports: 12 | - name: https 13 | port: 8443 14 | protocol: TCP 15 | targetPort: 8443 16 | selector: 17 | control-plane: controller-manager 18 | app.kubernetes.io/name: keystone-operator 19 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | patches: 6 | - path: patches/basic.config.yaml 7 | target: 8 | group: scorecard.operatorframework.io 9 | kind: Configuration 10 | name: config 11 | version: v1alpha3 12 | - path: patches/olm.config.yaml 13 | target: 14 | group: scorecard.operatorframework.io 15 | kind: Configuration 16 | name: config 17 | version: v1alpha3 18 | # +kubebuilder:scaffold:patches 19 | -------------------------------------------------------------------------------- /config/certmanager/issuer.yaml: -------------------------------------------------------------------------------- 1 | # The following manifest contains a self-signed issuer CR. 2 | # More information can be found at https://docs.cert-manager.io 3 | # WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. 4 | apiVersion: cert-manager.io/v1 5 | kind: Issuer 6 | metadata: 7 | labels: 8 | app.kubernetes.io/name: keystone-operator 9 | app.kubernetes.io/managed-by: kustomize 10 | name: selfsigned-issuer 11 | namespace: system 12 | spec: 13 | selfSigned: {} 14 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | 4 | # [PROMETHEUS-WITH-CERTS] The following patch configures the ServiceMonitor in ../prometheus 5 | # to securely reference certificates created and managed by cert-manager. 6 | # Additionally, ensure that you uncomment the [METRICS WITH CERTMANAGER] patch under config/default/kustomization.yaml 7 | # to mount the "metrics-server-cert" secret in the Manager Deployment. 8 | #patches: 9 | # - path: monitor_tls_patch.yaml 10 | # target: 11 | # kind: ServiceMonitor 12 | -------------------------------------------------------------------------------- /test/kuttl/common/scripts/test_invalid_token.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | 4 | export OS_TOKEN=$(cat /tmp/temporary_test_token) 5 | 6 | output=$(oc exec -tn $NAMESPACE openstackclient -- env -u OS_CLOUD - OS_AUTH_URL=http://keystone-public.keystone-kuttl-tests.svc:5000 OS_AUTH_TYPE=token OS_TOKEN=$OS_TOKEN openstack endpoint list 2>&1) 7 | 8 | filtered_output=$(echo "$output" | grep -i "Could not recognize Fernet token") 9 | 10 | if echo "$filtered_output" | grep -q "Could not recognize Fernet token"; then 11 | exit 0 12 | else 13 | exit 1 14 | fi 15 | -------------------------------------------------------------------------------- /test/kuttl/common/scripts/check_debug_in_keystone_pod_logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | found=0 4 | not_found=1 5 | if [ "$1" = "--reverse" ];then 6 | # sometimes we want to check that there is no DEBUG logs 7 | found=1 8 | not_found=0 9 | fi 10 | 11 | pod=$(oc get pods -n $NAMESPACE -l service=keystone -o name) 12 | # in the case the logs have DEBUG lines, keep only one to avoid cluttering the 13 | # test output 14 | debug=$(oc logs -n $NAMESPACE "$pod" | grep "DEBUG" | head -n 1) 15 | 16 | if [ -n "$debug" ]; then 17 | exit $found 18 | else 19 | exit $not_found 20 | fi 21 | -------------------------------------------------------------------------------- /.github/workflows/force-bump-pr-manual.yaml: -------------------------------------------------------------------------------- 1 | name: Manually Trigger a Force Bump PR 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | call-build-workflow: 8 | uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/force-bump-pull-request.yaml@main 9 | with: 10 | operator_name: keystone 11 | branch_name: ${{ github.ref_name }} 12 | custom_image: quay.io/openstack-k8s-operators/openstack-k8s-operators-ci-build-tools:golang-1.24-sdk-1.41.1 13 | secrets: 14 | FORCE_BUMP_PULL_REQUEST_PAT: ${{ secrets.FORCE_BUMP_PULL_REQUEST_PAT }} 15 | -------------------------------------------------------------------------------- /zuul.d/projects.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - project: 3 | name: openstack-k8s-operators/keystone-operator 4 | github-check: 5 | jobs: 6 | - openstack-k8s-operators-content-provider: 7 | vars: 8 | cifmw_install_yamls_sdk_version: v1.41.1 9 | - keystone-operator-kuttl: 10 | dependencies: 11 | - openstack-k8s-operators-content-provider 12 | voting: false 13 | - keystone-operator-tempest: 14 | dependencies: 15 | - openstack-k8s-operators-content-provider 16 | voting: false 17 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /kuttl-test.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # EXECUTION (from install_yamls repo root): 3 | # 4 | # make keystone_kuttl 5 | # 6 | # ASSUMPTIONS: 7 | # 8 | # 1. Latest version of kuttl is installed at /usr/local/bin/kubectl-kuttl 9 | # 2. An OCP 4.10+ CRC cluster with Podified Operators has been deployed 10 | # 3. CLI user has access to $KUBECONFIG 11 | 12 | apiVersion: kuttl.dev/v1beta1 13 | kind: TestSuite 14 | reportFormat: xml 15 | reportName: kuttl-report-keystone 16 | reportGranularity: test 17 | timeout: 800 18 | namespace: keystone-kuttl-tests 19 | parallel: 1 20 | suppress: 21 | - events # Remove spammy event logs 22 | -------------------------------------------------------------------------------- /.github/workflows/force-bump-pr-scheduled.yaml: -------------------------------------------------------------------------------- 1 | name: Scheduled Force Bump PR 2 | 3 | on: 4 | schedule: 5 | - cron: '0 3 * * 6' # 3AM UTC Saturday 6 | 7 | jobs: 8 | call-build-workflow: 9 | if: github.ref == 'refs/heads/main' && github.repository_owner == 'openstack-k8s-operators' 10 | uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/force-bump-branches.yaml@main 11 | with: 12 | operator_name: keystone 13 | custom_image: quay.io/openstack-k8s-operators/openstack-k8s-operators-ci-build-tools:golang-1.24-sdk-1.41.1 14 | secrets: 15 | FORCE_BUMP_PULL_REQUEST_PAT: ${{ secrets.FORCE_BUMP_PULL_REQUEST_PAT }} 16 | -------------------------------------------------------------------------------- /config/prometheus/monitor_tls_patch.yaml: -------------------------------------------------------------------------------- 1 | # Patch for Prometheus ServiceMonitor to enable secure TLS configuration 2 | # using certificates managed by cert-manager 3 | - op: replace 4 | path: /spec/endpoints/0/tlsConfig 5 | value: 6 | # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize 7 | serverName: SERVICE_NAME.SERVICE_NAMESPACE.svc 8 | insecureSkipVerify: false 9 | ca: 10 | secret: 11 | name: metrics-server-cert 12 | key: ca.crt 13 | cert: 14 | secret: 15 | name: metrics-server-cert 16 | key: tls.crt 17 | keySecret: 18 | name: metrics-server-cert 19 | key: tls.key 20 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_scale/01-assert.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # - 1 KeystoneAPI CR 5 | # - 3 Pods for KeystoneAPI CR 6 | # - 1 CronJob for KeystoneAPI CR 7 | # 8 | 9 | apiVersion: keystone.openstack.org/v1beta1 10 | kind: KeystoneAPI 11 | metadata: 12 | finalizers: 13 | - openstack.org/keystoneapi 14 | name: keystone 15 | spec: 16 | replicas: 3 17 | status: 18 | readyCount: 3 19 | --- 20 | apiVersion: apps/v1 21 | kind: Deployment 22 | metadata: 23 | name: keystone 24 | spec: 25 | replicas: 3 26 | status: 27 | availableReplicas: 3 28 | --- 29 | apiVersion: batch/v1 30 | kind: CronJob 31 | metadata: 32 | name: keystone-cron 33 | spec: 34 | schedule: 1 * * * * 35 | suspend: false 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | /bin 9 | testbin/* 10 | 11 | # Test binary, build with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Kubernetes Generated files - skip generated files, except for vendored files 18 | 19 | !vendor/**/zz_generated.* 20 | 21 | #Operator SDK generated files 22 | /bundle/ 23 | bundle.Dockerfile 24 | config/manager/kustomization.yaml 25 | 26 | # editor and IDE paraphernalia 27 | .idea 28 | *.swp 29 | *.swo 30 | *~ 31 | 32 | # Common CI tools repository 33 | CI_TOOLS_REPO 34 | 35 | # generated workspace file 36 | go.work 37 | go.work.sum 38 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "github>openstack-k8s-operators/renovate-config:default.json5" 4 | ], 5 | "baseBranchPatterns": [ 6 | "main" 7 | ], 8 | "useBaseBranchConfig": "merge", 9 | "packageRules": [ 10 | { 11 | "matchPackageNames": [ 12 | "github.com/openstack-k8s-operators/keystone-operator/api" 13 | ], 14 | "enabled": false 15 | } 16 | ], 17 | "postUpgradeTasks": { 18 | "commands": [ 19 | "make gowork", 20 | "make tidy", 21 | "make manifests generate" 22 | ], 23 | "fileFilters": [ 24 | "**/go.mod", 25 | "**/go.sum", 26 | "**/*.go", 27 | "**/*.yaml" 28 | ], 29 | "executionMode": "update" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_resources/01-assert.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # - 1 KeystoneAPI CR 5 | # - 1 KeystoneService CR 6 | # 7 | apiVersion: keystone.openstack.org/v1beta1 8 | kind: KeystoneAPI 9 | metadata: 10 | finalizers: 11 | - openstack.org/keystoneapi 12 | - openstack.org/keystoneservice-placement 13 | name: keystone 14 | --- 15 | apiVersion: keystone.openstack.org/v1beta1 16 | kind: KeystoneService 17 | metadata: 18 | finalizers: 19 | - openstack.org/keystoneservice 20 | name: placement 21 | spec: 22 | enabled: true 23 | passwordSelector: PlacementPassword 24 | secret: osp-secret 25 | serviceDescription: Placement service 26 | serviceName: placement 27 | serviceType: placement 28 | serviceUser: placement 29 | -------------------------------------------------------------------------------- /templates/keystoneapi/config/ssl.conf: -------------------------------------------------------------------------------- 1 | 2 | SSLRandomSeed startup builtin 3 | SSLRandomSeed startup file:/dev/urandom 512 4 | SSLRandomSeed connect builtin 5 | SSLRandomSeed connect file:/dev/urandom 512 6 | 7 | AddType application/x-x509-ca-cert .crt 8 | AddType application/x-pkcs7-crl .crl 9 | 10 | SSLPassPhraseDialog builtin 11 | SSLSessionCache "shmcb:/var/cache/mod_ssl/scache(512000)" 12 | SSLSessionCacheTimeout 300 13 | Mutex default 14 | SSLCryptoDevice builtin 15 | SSLHonorCipherOrder On 16 | SSLUseStapling Off 17 | SSLStaplingCache "shmcb:/run/httpd/ssl_stapling(32768)" 18 | SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!RC4:!3DES 19 | SSLProtocol all -SSLv2 -SSLv3 -TLSv1 20 | SSLOptions StdEnvVars 21 | 22 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: keystone-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: leader-election-role 9 | rules: 10 | - apiGroups: 11 | - "" 12 | resources: 13 | - configmaps 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - create 19 | - update 20 | - patch 21 | - delete 22 | - apiGroups: 23 | - coordination.k8s.io 24 | resources: 25 | - leases 26 | verbs: 27 | - get 28 | - list 29 | - watch 30 | - create 31 | - update 32 | - patch 33 | - delete 34 | - apiGroups: 35 | - "" 36 | resources: 37 | - events 38 | verbs: 39 | - create 40 | - patch 41 | -------------------------------------------------------------------------------- /zuul.d/jobs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - job: 3 | name: keystone-operator-kuttl 4 | parent: cifmw-base-multinode-kuttl 5 | attempts: 1 6 | required-projects: 7 | - github.com/openstack-k8s-operators/keystone-operator 8 | irrelevant-files: 9 | - .*/*.md 10 | - ^\..*$ 11 | - ^LICENSE$ 12 | - ^OWNERS$ 13 | - ^OWNERS_ALIASES$ 14 | - ^PROJECT$ 15 | - ^README.md$ 16 | - tests?\/functional 17 | - ^renovate.json$ 18 | vars: 19 | cifmw_kuttl_tests_operator_list: 20 | - keystone 21 | 22 | - job: 23 | name: keystone-operator-tempest 24 | parent: podified-multinode-edpm-deployment-crc-2comp 25 | vars: 26 | cifmw_test_operator_tempest_concurrency: 3 27 | cifmw_test_operator_tempest_include_list: | 28 | ^tempest.api.identity. 29 | -------------------------------------------------------------------------------- /config/certmanager/certificate-webhook.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | apiVersion: cert-manager.io/v1 4 | kind: Certificate 5 | metadata: 6 | labels: 7 | app.kubernetes.io/name: keystone-operator 8 | app.kubernetes.io/managed-by: kustomize 9 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 10 | namespace: system 11 | spec: 12 | # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize 13 | # replacements in the config/default/kustomization.yaml file. 14 | dnsNames: 15 | - SERVICE_NAME.SERVICE_NAMESPACE.svc 16 | - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local 17 | issuerRef: 18 | kind: Issuer 19 | name: selfsigned-issuer 20 | secretName: webhook-server-cert 21 | -------------------------------------------------------------------------------- /.github/workflows/build-keystone-operator.yaml: -------------------------------------------------------------------------------- 1 | name: keystone operator image builder 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | 8 | env: 9 | imageregistry: 'quay.io' 10 | imagenamespace: ${{ secrets.IMAGENAMESPACE || secrets.QUAY_USERNAME }} 11 | latesttag: latest 12 | 13 | jobs: 14 | call-build-workflow: 15 | uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/reusable-build-operator.yaml@main 16 | with: 17 | operator_name: keystone 18 | go_version: 1.24.x 19 | operator_sdk_version: 1.41.1 20 | secrets: 21 | IMAGENAMESPACE: ${{ secrets.IMAGENAMESPACE }} 22 | QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }} 23 | QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }} 24 | REDHATIO_USERNAME: ${{ secrets.REDHATIO_USERNAME }} 25 | REDHATIO_PASSWORD: ${{ secrets.REDHATIO_PASSWORD }} 26 | -------------------------------------------------------------------------------- /config/certmanager/certificate-metrics.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a metrics certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | apiVersion: cert-manager.io/v1 4 | kind: Certificate 5 | metadata: 6 | labels: 7 | app.kubernetes.io/name: keystone-operator 8 | app.kubernetes.io/managed-by: kustomize 9 | name: metrics-certs # this name should match the one appeared in kustomizeconfig.yaml 10 | namespace: system 11 | spec: 12 | dnsNames: 13 | # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize 14 | # replacements in the config/default/kustomization.yaml file. 15 | - SERVICE_NAME.SERVICE_NAMESPACE.svc 16 | - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local 17 | issuerRef: 18 | kind: Issuer 19 | name: selfsigned-issuer 20 | secretName: metrics-server-cert 21 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting nameReference. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/keystone.openstack.org_keystoneapis.yaml 6 | - bases/keystone.openstack.org_keystoneservices.yaml 7 | - bases/keystone.openstack.org_keystoneendpoints.yaml 8 | # +kubebuilder:scaffold:crdkustomizeresource 9 | 10 | patches: 11 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 12 | # patches here are for enabling the conversion webhook for each CRD 13 | # +kubebuilder:scaffold:crdkustomizewebhookpatch 14 | 15 | # [WEBHOOK] To enable webhook, uncomment the following section 16 | # the following config is for teaching kustomize how to do kustomization for CRDs. 17 | #configurations: 18 | #- kustomizeconfig.yaml 19 | -------------------------------------------------------------------------------- /config/rbac/keystoneapi_admin_role.yaml: -------------------------------------------------------------------------------- 1 | # This rule is not used by the project keystone-operator itself. 2 | # It is provided to allow the cluster admin to help manage permissions for users. 3 | # 4 | # Grants full permissions ('*') over keystone.openstack.org. 5 | # This role is intended for users authorized to modify roles and bindings within the cluster, 6 | # enabling them to delegate specific permissions to other users or groups as needed. 7 | 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: keystone-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | name: keystoneapi-admin-role 15 | rules: 16 | - apiGroups: 17 | - keystone.openstack.org 18 | resources: 19 | - keystoneapis 20 | verbs: 21 | - '*' 22 | - apiGroups: 23 | - keystone.openstack.org 24 | resources: 25 | - keystoneapis/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_scale/03-assert.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # - 0 KeystoneAPI CR 5 | # - Keystone Deployment with 0 Pods 6 | # 7 | 8 | apiVersion: keystone.openstack.org/v1beta1 9 | kind: KeystoneAPI 10 | metadata: 11 | finalizers: 12 | - openstack.org/keystoneapi 13 | name: keystone 14 | spec: 15 | adminProject: admin 16 | adminUser: admin 17 | customServiceConfig: | 18 | [DEFAULT] 19 | debug = true 20 | databaseInstance: openstack 21 | databaseAccount: keystone 22 | passwordSelectors: 23 | admin: AdminPassword 24 | preserveJobs: false 25 | region: regionOne 26 | replicas: 0 27 | resources: 28 | requests: 29 | cpu: "1" 30 | memory: 500Mi 31 | secret: osp-secret 32 | status: 33 | databaseHostname: openstack.keystone-kuttl-tests.svc 34 | --- 35 | apiVersion: apps/v1 36 | kind: Deployment 37 | metadata: 38 | name: keystone 39 | spec: 40 | replicas: 0 41 | -------------------------------------------------------------------------------- /hack/crd-schema-checker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | 4 | CHECKER=$INSTALL_DIR/crd-schema-checker 5 | DISABLED_VALIDATORS=NoMaps,NoBools,ListsMustHaveSSATags # TODO: https://issues.redhat.com/browse/OSPRH-12254 6 | 7 | CHECKER_ARGS="" 8 | if [[ ${DISABLED_VALIDATORS:+x} ]]; then 9 | CHECKER_ARGS="$CHECKER_ARGS " 10 | for check in ${DISABLED_VALIDATORS//,/ }; do 11 | CHECKER_ARGS+=" --disabled-validators $check" 12 | done 13 | fi 14 | 15 | TMP_DIR=$(mktemp -d) 16 | 17 | function cleanup { 18 | rm -rf "$TMP_DIR" 19 | } 20 | 21 | trap cleanup EXIT 22 | 23 | 24 | for crd in config/crd/bases/*.yaml; do 25 | mkdir -p "$(dirname "$TMP_DIR/$crd")" 26 | if git show "$BASE_REF:$crd" > "$TMP_DIR/$crd"; then 27 | $CHECKER check-manifests \ 28 | $CHECKER_ARGS \ 29 | --existing-crd-filename="$TMP_DIR/$crd" \ 30 | --new-crd-filename="$crd" 31 | fi 32 | done 33 | -------------------------------------------------------------------------------- /config/rbac/keystoneendpoint_admin_role.yaml: -------------------------------------------------------------------------------- 1 | # This rule is not used by the project keystone-operator itself. 2 | # It is provided to allow the cluster admin to help manage permissions for users. 3 | # 4 | # Grants full permissions ('*') over keystone.openstack.org. 5 | # This role is intended for users authorized to modify roles and bindings within the cluster, 6 | # enabling them to delegate specific permissions to other users or groups as needed. 7 | 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: keystone-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | name: keystoneendpoint-admin-role 15 | rules: 16 | - apiGroups: 17 | - keystone.openstack.org 18 | resources: 19 | - keystoneendpoints 20 | verbs: 21 | - '*' 22 | - apiGroups: 23 | - keystone.openstack.org 24 | resources: 25 | - keystoneendpoints/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/keystoneservice_admin_role.yaml: -------------------------------------------------------------------------------- 1 | # This rule is not used by the project keystone-operator itself. 2 | # It is provided to allow the cluster admin to help manage permissions for users. 3 | # 4 | # Grants full permissions ('*') over keystone.openstack.org. 5 | # This role is intended for users authorized to modify roles and bindings within the cluster, 6 | # enabling them to delegate specific permissions to other users or groups as needed. 7 | 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: keystone-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | name: keystoneservice-admin-role 15 | rules: 16 | - apiGroups: 17 | - keystone.openstack.org 18 | resources: 19 | - keystoneservices 20 | verbs: 21 | - '*' 22 | - apiGroups: 23 | - keystone.openstack.org 24 | resources: 25 | - keystoneservices/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/keystoneapi_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # This rule is not used by the project keystone-operator itself. 2 | # It is provided to allow the cluster admin to help manage permissions for users. 3 | # 4 | # Grants read-only access to keystone.openstack.org resources. 5 | # This role is intended for users who need visibility into these resources 6 | # without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing. 7 | 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: keystone-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | name: keystoneapi-viewer-role 15 | rules: 16 | - apiGroups: 17 | - keystone.openstack.org 18 | resources: 19 | - keystoneapis 20 | verbs: 21 | - get 22 | - list 23 | - watch 24 | - apiGroups: 25 | - keystone.openstack.org 26 | resources: 27 | - keystoneapis/status 28 | verbs: 29 | - get 30 | -------------------------------------------------------------------------------- /config/samples/keystone_v1beta1_keystoneapi_tls.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: keystone.openstack.org/v1beta1 2 | kind: KeystoneAPI 3 | metadata: 4 | name: keystone 5 | spec: 6 | adminProject: admin 7 | adminUser: admin 8 | customServiceConfig: | 9 | [DEFAULT] 10 | debug = true 11 | databaseInstance: openstack 12 | databaseAccount: keystone 13 | preserveJobs: false 14 | region: regionOne 15 | secret: osp-secret 16 | resources: 17 | requests: 18 | memory: "500Mi" 19 | cpu: "1.0" 20 | tls: 21 | api: 22 | # secret holding tls.crt and tls.key for the APIs internal k8s service 23 | internal: 24 | secretName: cert-keystone-internal-svc 25 | # secret holding tls.crt and tls.key for the APIs public k8s service 26 | public: 27 | secretName: cert-keystone-public-svc 28 | # secret holding the tls-ca-bundle.pem to be used as a deploymend env CA bundle 29 | caBundleSecretName: combined-ca-bundle 30 | -------------------------------------------------------------------------------- /config/rbac/keystoneservice_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # This rule is not used by the project keystone-operator itself. 2 | # It is provided to allow the cluster admin to help manage permissions for users. 3 | # 4 | # Grants read-only access to keystone.openstack.org resources. 5 | # This role is intended for users who need visibility into these resources 6 | # without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing. 7 | 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: keystone-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | name: keystoneservice-viewer-role 15 | rules: 16 | - apiGroups: 17 | - keystone.openstack.org 18 | resources: 19 | - keystoneservices 20 | verbs: 21 | - get 22 | - list 23 | - watch 24 | - apiGroups: 25 | - keystone.openstack.org 26 | resources: 27 | - keystoneservices/status 28 | verbs: 29 | - get 30 | -------------------------------------------------------------------------------- /config/rbac/keystoneendpoint_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # This rule is not used by the project keystone-operator itself. 2 | # It is provided to allow the cluster admin to help manage permissions for users. 3 | # 4 | # Grants read-only access to keystone.openstack.org resources. 5 | # This role is intended for users who need visibility into these resources 6 | # without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing. 7 | 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: keystone-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | name: keystoneendpoint-viewer-role 15 | rules: 16 | - apiGroups: 17 | - keystone.openstack.org 18 | resources: 19 | - keystoneendpoints 20 | verbs: 21 | - get 22 | - list 23 | - watch 24 | - apiGroups: 25 | - keystone.openstack.org 26 | resources: 27 | - keystoneendpoints/status 28 | verbs: 29 | - get 30 | -------------------------------------------------------------------------------- /test/kuttl/common/scripts/rotate_token.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | 4 | TMP_SECRET_FILE="/tmp/keystone-secret.yaml" 5 | 6 | generate_secret_yaml() { 7 | cat < $TMP_SECRET_FILE 8 | apiVersion: v1 9 | kind: Secret 10 | metadata: 11 | name: keystone 12 | namespace: keystone-kuttl-tests 13 | annotations: 14 | keystone.openstack.org/rotatedat: "2009-11-10T23:00:00Z" 15 | EOF 16 | } 17 | 18 | for rotation in {1..5}; do 19 | echo "Starting rotation $rotation..." 20 | 21 | # Apply new secret to trigger rotation 22 | generate_secret_yaml 23 | if ! oc apply -f $TMP_SECRET_FILE; then 24 | echo "Failed to apply the secret!" 25 | rm -f $TMP_SECRET_FILE 26 | exit 1 27 | fi 28 | 29 | sleep 100 30 | 31 | # Note: keystone is not being restarted 32 | 33 | echo "Rotation $rotation completed successfully." 34 | done 35 | 36 | rm -f $TMP_SECRET_FILE 37 | echo "All rotations completed successfully." 38 | exit 0 39 | -------------------------------------------------------------------------------- /config/network-policy/allow-metrics-traffic.yaml: -------------------------------------------------------------------------------- 1 | # This NetworkPolicy allows ingress traffic 2 | # with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those 3 | # namespaces are able to gather data from the metrics endpoint. 4 | apiVersion: networking.k8s.io/v1 5 | kind: NetworkPolicy 6 | metadata: 7 | labels: 8 | app.kubernetes.io/name: keystone-operator 9 | app.kubernetes.io/managed-by: kustomize 10 | name: allow-metrics-traffic 11 | namespace: system 12 | spec: 13 | podSelector: 14 | matchLabels: 15 | control-plane: controller-manager 16 | app.kubernetes.io/name: keystone-operator 17 | policyTypes: 18 | - Ingress 19 | ingress: 20 | # This allows ingress traffic from any namespace with the label metrics: enabled 21 | - from: 22 | - namespaceSelector: 23 | matchLabels: 24 | metrics: enabled # Only from namespaces with this label 25 | ports: 26 | - port: 8443 27 | protocol: TCP 28 | -------------------------------------------------------------------------------- /test/kuttl/common/errors_cleanup_keystone.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # No KeystoneAPI CR 5 | # No Deployment for KeystoneAPI CR 6 | # No Pods in keystone Deployment 7 | # No Keystone Services 8 | # 9 | apiVersion: keystone.openstack.org/v1beta1 10 | kind: KeystoneAPI 11 | metadata: 12 | finalizers: 13 | - openstack.org/keystoneapi 14 | name: keystone 15 | --- 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: keystone 20 | --- 21 | # the openshift annotations can't be checked through the deployment above 22 | apiVersion: v1 23 | kind: Pod 24 | metadata: 25 | annotations: 26 | openshift.io/scc: anyuid 27 | labels: 28 | service: keystone 29 | --- 30 | apiVersion: v1 31 | kind: Service 32 | metadata: 33 | labels: 34 | endpoint: internal 35 | service: keystone 36 | name: keystone-internal 37 | --- 38 | apiVersion: v1 39 | kind: Service 40 | metadata: 41 | labels: 42 | endpoint: public 43 | service: keystone 44 | name: keystone-public 45 | -------------------------------------------------------------------------------- /config/rbac/keystoneapi_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # This rule is not used by the project keystone-operator itself. 2 | # It is provided to allow the cluster admin to help manage permissions for users. 3 | # 4 | # Grants permissions to create, update, and delete resources within the keystone.openstack.org. 5 | # This role is intended for users who need to manage these resources 6 | # but should not control RBAC or manage permissions for others. 7 | 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: keystone-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | name: keystoneapi-editor-role 15 | rules: 16 | - apiGroups: 17 | - keystone.openstack.org 18 | resources: 19 | - keystoneapis 20 | verbs: 21 | - create 22 | - delete 23 | - get 24 | - list 25 | - patch 26 | - update 27 | - watch 28 | - apiGroups: 29 | - keystone.openstack.org 30 | resources: 31 | - keystoneapis/status 32 | verbs: 33 | - get 34 | -------------------------------------------------------------------------------- /test/kuttl/common/scripts/validate_test_token.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -euxo pipefail 3 | 4 | seconds=1 5 | while [ $seconds -le 30 ]; do 6 | rotatedat=$(oc get secret keystone -n $NAMESPACE -o jsonpath="{.metadata.annotations['keystone\.openstack\.org/rotatedat']}") 7 | if [ "$rotatedat" != "2009-11-10T23:00:00Z" ]; then 8 | break 9 | fi 10 | sleep 1 11 | seconds=$(( seconds + 1 )) 12 | done 13 | 14 | # Wait for secret propagation to pods instead of waiting for rollout 15 | # Since pods no longer restart during key rotation, keys are updated via Kubernetes secret propagation 16 | sleep 60 17 | 18 | export OS_TOKEN=$(cat /tmp/temporary_test_token) 19 | 20 | output=$(oc exec -tn $NAMESPACE openstackclient -- env -u OS_CLOUD - OS_AUTH_URL=http://keystone-public.keystone-kuttl-tests.svc:5000 OS_AUTH_TYPE=token OS_TOKEN=$OS_TOKEN openstack endpoint list 2>&1 || true) 21 | 22 | if echo "$output" | grep -qi "Could not recognize Fernet token"; then 23 | exit 1 24 | else 25 | exit 0 26 | fi 27 | -------------------------------------------------------------------------------- /config/rbac/keystoneservice_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # This rule is not used by the project keystone-operator itself. 2 | # It is provided to allow the cluster admin to help manage permissions for users. 3 | # 4 | # Grants permissions to create, update, and delete resources within the keystone.openstack.org. 5 | # This role is intended for users who need to manage these resources 6 | # but should not control RBAC or manage permissions for others. 7 | 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: keystone-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | name: keystoneservice-editor-role 15 | rules: 16 | - apiGroups: 17 | - keystone.openstack.org 18 | resources: 19 | - keystoneservices 20 | verbs: 21 | - create 22 | - delete 23 | - get 24 | - list 25 | - patch 26 | - update 27 | - watch 28 | - apiGroups: 29 | - keystone.openstack.org 30 | resources: 31 | - keystoneservices/status 32 | verbs: 33 | - get 34 | -------------------------------------------------------------------------------- /config/rbac/keystoneendpoint_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # This rule is not used by the project keystone-operator itself. 2 | # It is provided to allow the cluster admin to help manage permissions for users. 3 | # 4 | # Grants permissions to create, update, and delete resources within the keystone.openstack.org. 5 | # This role is intended for users who need to manage these resources 6 | # but should not control RBAC or manage permissions for others. 7 | 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: keystone-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | name: keystoneendpoint-editor-role 15 | rules: 16 | - apiGroups: 17 | - keystone.openstack.org 18 | resources: 19 | - keystoneendpoints 20 | verbs: 21 | - create 22 | - delete 23 | - get 24 | - list 25 | - patch 26 | - update 27 | - watch 28 | - apiGroups: 29 | - keystone.openstack.org 30 | resources: 31 | - keystoneendpoints/status 32 | verbs: 33 | - get 34 | -------------------------------------------------------------------------------- /config/default/cert_metrics_manager_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch adds the args, volumes, and ports to allow the manager to use the metrics-server certs. 2 | 3 | # Add the volumeMount for the metrics-server certs 4 | - op: add 5 | path: /spec/template/spec/containers/0/volumeMounts/- 6 | value: 7 | mountPath: /tmp/k8s-metrics-server/metrics-certs 8 | name: metrics-certs 9 | readOnly: true 10 | 11 | # Add the --metrics-cert-path argument for the metrics server 12 | - op: add 13 | path: /spec/template/spec/containers/0/args/- 14 | value: --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs 15 | 16 | # Add the metrics-server certs volume configuration 17 | - op: add 18 | path: /spec/template/spec/volumes/- 19 | value: 20 | name: metrics-certs 21 | secret: 22 | secretName: metrics-server-cert 23 | optional: false 24 | items: 25 | - key: ca.crt 26 | path: ca.crt 27 | - key: tls.crt 28 | path: tls.crt 29 | - key: tls.key 30 | path: tls.key 31 | -------------------------------------------------------------------------------- /config/network-policy/allow-webhook-traffic.yaml: -------------------------------------------------------------------------------- 1 | # This NetworkPolicy allows ingress traffic to your webhook server running 2 | # as part of the controller-manager from specific namespaces and pods. CR(s) which uses webhooks 3 | # will only work when applied in namespaces labeled with 'webhook: enabled' 4 | apiVersion: networking.k8s.io/v1 5 | kind: NetworkPolicy 6 | metadata: 7 | labels: 8 | app.kubernetes.io/name: keystone-operator 9 | app.kubernetes.io/managed-by: kustomize 10 | name: allow-webhook-traffic 11 | namespace: system 12 | spec: 13 | podSelector: 14 | matchLabels: 15 | control-plane: controller-manager 16 | app.kubernetes.io/name: keystone-operator 17 | policyTypes: 18 | - Ingress 19 | ingress: 20 | # This allows ingress traffic from any namespace with the label webhook: enabled 21 | - from: 22 | - namespaceSelector: 23 | matchLabels: 24 | webhook: enabled # Only from namespaces with this label 25 | ports: 26 | - port: 443 27 | protocol: TCP 28 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_resources/02-assert.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # - 1 KeystoneAPI CR 5 | # - 1 KeystoneService CR 6 | # - 1 KeystoneEndpoint CR 7 | # 8 | apiVersion: keystone.openstack.org/v1beta1 9 | kind: KeystoneAPI 10 | metadata: 11 | finalizers: 12 | - openstack.org/keystoneapi 13 | - openstack.org/keystoneservice-placement 14 | - openstack.org/keystoneendpoint-placement 15 | name: keystone 16 | --- 17 | apiVersion: keystone.openstack.org/v1beta1 18 | kind: KeystoneService 19 | metadata: 20 | finalizers: 21 | - openstack.org/keystoneservice 22 | - openstack.org/keystoneendpoint-placement 23 | name: placement 24 | --- 25 | apiVersion: keystone.openstack.org/v1beta1 26 | kind: KeystoneEndpoint 27 | metadata: 28 | finalizers: 29 | - openstack.org/keystoneendpoint 30 | name: placement 31 | spec: 32 | endpoints: 33 | admin: http://placement-admin-openstack.apps-crc.testing 34 | internal: http://placement-internal-openstack.apps-crc.testing 35 | public: http://placement-public-openstack.apps-crc.testing 36 | serviceName: placement 37 | -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch ensures the webhook certificates are properly mounted in the manager container. 2 | # It configures the necessary arguments, volumes, volume mounts, and container ports. 3 | 4 | # Add the --webhook-cert-path argument for configuring the webhook certificate path 5 | - op: add 6 | path: /spec/template/spec/containers/0/args/- 7 | value: --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs 8 | 9 | # Add the volumeMount for the webhook certificates 10 | - op: add 11 | path: /spec/template/spec/containers/0/volumeMounts/- 12 | value: 13 | mountPath: /tmp/k8s-webhook-server/serving-certs 14 | name: webhook-certs 15 | readOnly: true 16 | 17 | # Add the port configuration for the webhook server 18 | - op: add 19 | path: /spec/template/spec/containers/0/ports/- 20 | value: 21 | containerPort: 9443 22 | name: webhook-server 23 | protocol: TCP 24 | 25 | # Add the volume configuration for the webhook certificates 26 | - op: add 27 | path: /spec/template/spec/volumes/- 28 | value: 29 | name: webhook-certs 30 | secret: 31 | secretName: webhook-server-cert 32 | -------------------------------------------------------------------------------- /internal/keystone/fernet.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package keystone 17 | 18 | import ( 19 | "crypto/rand" 20 | "encoding/base64" 21 | 22 | "github.com/go-logr/logr" 23 | ) 24 | 25 | // GenerateFernetKey - returns a base64-encoded, 32-byte key using cryptographically secure random generation 26 | func GenerateFernetKey(logger logr.Logger) string { 27 | data := make([]byte, 32) 28 | _, err := rand.Read(data) 29 | if err != nil { 30 | logger.Error(err, "failed to read random bytes for Fernet key generation") 31 | return "" 32 | } 33 | 34 | return base64.StdEncoding.EncodeToString(data) 35 | } 36 | -------------------------------------------------------------------------------- /config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # These resources constitute the fully configured set of manifests 2 | # used to generate the 'manifests/' directory in a bundle. 3 | resources: 4 | - bases/keystone-operator.clusterserviceversion.yaml 5 | - ../default 6 | - ../samples 7 | - ../scorecard 8 | 9 | # [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. 10 | # Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. 11 | # These patches remove the unnecessary "cert" volume and its manager container volumeMount. 12 | #patches: 13 | #- target: 14 | # group: apps 15 | # version: v1 16 | # kind: Deployment 17 | # name: controller-manager 18 | # namespace: system 19 | # patch: |- 20 | # # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. 21 | # # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. 22 | # - op: remove 23 | 24 | # path: /spec/template/spec/containers/0/volumeMounts/0 25 | # # Remove the "cert" volume, since OLM will create and mount a set of certs. 26 | # # Update the indices in this path if adding or removing volumes in the manager's Deployment. 27 | # - op: remove 28 | # path: /spec/template/spec/volumes/0 29 | -------------------------------------------------------------------------------- /templates/keystoneapi/config/keystone.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | use_stderr=true 3 | 4 | [cache] 5 | {{if .MemcachedTLS}} 6 | backend = oslo_cache.memcache_pool 7 | memcache_servers={{ .MemcachedServers }} 8 | memcache_socket_timeout = 0.5 9 | memcache_pool_connection_get_timeout = 1 10 | {{if (index . "MemcachedAuthCert")}} 11 | tls_certfile={{ .MemcachedAuthCert }} 12 | tls_keyfile={{ .MemcachedAuthKey }} 13 | tls_cafile={{ .MemcachedAuthCa }} 14 | {{end}} 15 | {{else}} 16 | backend = dogpile.cache.memcached 17 | memcache_servers={{ .MemcachedServersWithInet }} 18 | {{end}} 19 | memcache_dead_retry = 30 20 | enabled=true 21 | tls_enabled={{ .MemcachedTLS }} 22 | 23 | [database] 24 | max_retries=-1 25 | db_max_retries=-1 26 | connection={{ .DatabaseConnection }} 27 | 28 | [oslo_policy] 29 | enforce_new_defaults = {{ .EnableSecureRBAC }} 30 | enforce_scope = {{ .EnableSecureRBAC }} 31 | 32 | [fernet_tokens] 33 | key_repository=/etc/keystone/fernet-keys 34 | max_active_keys={{ .FernetMaxActiveKeys }} 35 | 36 | {{ if (index . "TransportURL") }} 37 | [oslo_messaging_notifications] 38 | driver=messagingv2 39 | transport_url={{ .TransportURL }} 40 | topics = barbican_notifications 41 | {{ if (index . "QuorumQueues") -}} 42 | [oslo_messaging_rabbit] 43 | rabbit_quorum_queue=true 44 | rabbit_transient_quorum_queue=true 45 | amqp_durable_queues=true 46 | {{- end -}} 47 | {{ end }} 48 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | # Prometheus Monitor Service (Metrics) 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | labels: 6 | control-plane: controller-manager 7 | app.kubernetes.io/name: keystone-operator 8 | app.kubernetes.io/managed-by: kustomize 9 | name: controller-manager-metrics-monitor 10 | namespace: system 11 | spec: 12 | endpoints: 13 | - path: /metrics 14 | port: https # Ensure this is the name of the port that exposes HTTPS metrics 15 | scheme: https 16 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 17 | tlsConfig: 18 | # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables 19 | # certificate verification, exposing the system to potential man-in-the-middle attacks. 20 | # For production environments, it is recommended to use cert-manager for automatic TLS certificate management. 21 | # To apply this configuration, enable cert-manager and use the patch located at config/prometheus/servicemonitor_tls_patch.yaml, 22 | # which securely references the certificate from the 'metrics-server-cert' secret. 23 | insecureSkipVerify: true 24 | selector: 25 | matchLabels: 26 | control-plane: controller-manager 27 | app.kubernetes.io/name: keystone-operator 28 | -------------------------------------------------------------------------------- /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: MutatingWebhookConfiguration 4 | metadata: 5 | name: mutating-webhook-configuration 6 | webhooks: 7 | - admissionReviewVersions: 8 | - v1 9 | clientConfig: 10 | service: 11 | name: webhook-service 12 | namespace: system 13 | path: /mutate-keystone-openstack-org-v1beta1-keystoneapi 14 | failurePolicy: Fail 15 | name: mkeystoneapi-v1beta1.kb.io 16 | rules: 17 | - apiGroups: 18 | - keystone.openstack.org 19 | apiVersions: 20 | - v1beta1 21 | operations: 22 | - CREATE 23 | - UPDATE 24 | resources: 25 | - keystoneapis 26 | sideEffects: None 27 | --- 28 | apiVersion: admissionregistration.k8s.io/v1 29 | kind: ValidatingWebhookConfiguration 30 | metadata: 31 | name: validating-webhook-configuration 32 | webhooks: 33 | - admissionReviewVersions: 34 | - v1 35 | clientConfig: 36 | service: 37 | name: webhook-service 38 | namespace: system 39 | path: /validate-keystone-openstack-org-v1beta1-keystoneapi 40 | failurePolicy: Fail 41 | name: vkeystoneapi-v1beta1.kb.io 42 | rules: 43 | - apiGroups: 44 | - keystone.openstack.org 45 | apiVersions: 46 | - v1beta1 47 | operations: 48 | - CREATE 49 | - UPDATE 50 | resources: 51 | - keystoneapis 52 | sideEffects: None 53 | -------------------------------------------------------------------------------- /api/v1beta1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1beta1 contains API Schema definitions for the keystone v1beta1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=keystone.openstack.org 20 | package v1beta1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "keystone.openstack.org", Version: "v1beta1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | # Code generated by tool. DO NOT EDIT. 2 | # This file is used to track the info used to scaffold your project 3 | # and allow the plugins properly work. 4 | # More info: https://book.kubebuilder.io/reference/project-config.html 5 | domain: openstack.org 6 | layout: 7 | - go.kubebuilder.io/v4 8 | plugins: 9 | manifests.sdk.operatorframework.io/v2: {} 10 | scorecard.sdk.operatorframework.io/v2: {} 11 | projectName: keystone-operator 12 | repo: github.com/openstack-k8s-operators/keystone-operator 13 | resources: 14 | - api: 15 | crdVersion: v1 16 | namespaced: true 17 | controller: true 18 | domain: openstack.org 19 | group: keystone 20 | kind: KeystoneAPI 21 | path: github.com/openstack-k8s-operators/keystone-operator/api/v1beta1 22 | version: v1beta1 23 | webhooks: 24 | defaulting: true 25 | validation: true 26 | webhookVersion: v1 27 | - api: 28 | crdVersion: v1 29 | namespaced: true 30 | controller: true 31 | domain: openstack.org 32 | group: keystone 33 | kind: KeystoneService 34 | path: github.com/openstack-k8s-operators/keystone-operator/api/v1beta1 35 | version: v1beta1 36 | - api: 37 | crdVersion: v1 38 | namespaced: true 39 | controller: true 40 | domain: openstack.org 41 | group: keystone 42 | kind: KeystoneEndpoint 43 | path: github.com/openstack-k8s-operators/keystone-operator/api/v1beta1 44 | version: v1beta1 45 | version: "3" 46 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.41.1 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.41.1 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.41.1 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.41.1 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.41.1 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /internal/keystone/funcs.go: -------------------------------------------------------------------------------- 1 | package keystone 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | "k8s.io/utils/ptr" 6 | ) 7 | 8 | // baseSecurityContext - currently used to make sure we don't run cronJob and Log 9 | // Pods as root user, and we drop privileges and Capabilities we don't need 10 | func baseSecurityContext() *corev1.SecurityContext { 11 | return &corev1.SecurityContext{ 12 | RunAsUser: ptr.To(KeystoneUID), 13 | RunAsGroup: ptr.To(KeystoneUID), 14 | RunAsNonRoot: ptr.To(true), 15 | AllowPrivilegeEscalation: ptr.To(false), 16 | Capabilities: &corev1.Capabilities{ 17 | Drop: []corev1.Capability{ 18 | "ALL", 19 | }, 20 | }, 21 | } 22 | } 23 | 24 | // dbSyncSecurityContext - currently used to make sure we don't run db-sync as 25 | // root user 26 | func dbSyncSecurityContext() *corev1.SecurityContext { 27 | return &corev1.SecurityContext{ 28 | RunAsUser: ptr.To(KeystoneUID), 29 | RunAsGroup: ptr.To(KeystoneUID), 30 | Capabilities: &corev1.Capabilities{ 31 | Drop: []corev1.Capability{ 32 | "MKNOD", 33 | }, 34 | }, 35 | } 36 | } 37 | 38 | // httpdSecurityContext - 39 | func httpdSecurityContext() *corev1.SecurityContext { 40 | return &corev1.SecurityContext{ 41 | Capabilities: &corev1.Capabilities{ 42 | Drop: []corev1.Capability{ 43 | "MKNOD", 44 | }, 45 | }, 46 | RunAsUser: ptr.To(KeystoneUID), 47 | RunAsGroup: ptr.To(KeystoneUID), 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.pull_request_pipeline: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent none 3 | stages { 4 | stage('Pull request proposed jobs') { 5 | when { 6 | branch 'PR-*' 7 | } 8 | parallel { 9 | stage('DFG-converged-openstack-k8s-keystone-operator-distgit-pr') { 10 | steps { 11 | build job: 'DFG-converged-openstack-k8s-keystone-operator-distgit-pr', 12 | parameters: [ 13 | string(name: 'GITHUB_PULL_REQUEST_ID', value: String.valueOf(CHANGE_ID)) 14 | ] 15 | } 16 | } 17 | stage('DFG-converged-openstack-k8s-keystone-operator-buildah') { 18 | steps { 19 | build job: 'DFG-converged-openstack-k8s-keystone-operator-buildah', 20 | parameters: [ 21 | string(name: 'GITHUB_PULL_REQUEST_ID', value: String.valueOf(CHANGE_ID)) 22 | ] 23 | } 24 | } 25 | } 26 | } 27 | stage('Pull request merged jobs') { 28 | when { 29 | allOf { 30 | environment name: 'CHANGE_ID', value: '' 31 | branch 'master' 32 | } 33 | } 34 | steps { 35 | build job: 'DFG-converged-openstack-k8s-keystone-operator-merge' 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # The following RBAC configurations are used to protect 13 | # the metrics endpoint with authn/authz. These configurations 14 | # ensure that only authorized users and service accounts 15 | # can access the metrics endpoint. Comment the following 16 | # permissions if you want to disable this protection. 17 | # More info: https://book.kubebuilder.io/reference/metrics.html 18 | - metrics_auth_role.yaml 19 | - metrics_auth_role_binding.yaml 20 | - metrics_reader_role.yaml 21 | # For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by 22 | # default, aiding admins in cluster management. Those roles are 23 | # not used by the keystone-operator itself. You can comment the following lines 24 | # if you do not want those helpers be installed with your Project. 25 | - keystoneendpoint_admin_role.yaml 26 | - keystoneendpoint_editor_role.yaml 27 | - keystoneendpoint_viewer_role.yaml 28 | - keystoneservice_admin_role.yaml 29 | - keystoneservice_editor_role.yaml 30 | - keystoneservice_viewer_role.yaml 31 | - keystoneapi_admin_role.yaml 32 | - keystoneapi_editor_role.yaml 33 | - keystoneapi_viewer_role.yaml 34 | -------------------------------------------------------------------------------- /test/kuttl/tests/fernet_rotation/01-deploy_openstackclient.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | annotations: 5 | openshift.io/scc: anyuid 6 | labels: 7 | app: openstackclient 8 | name: openstackclient 9 | spec: 10 | containers: 11 | - args: 12 | - --single-child 13 | - -- 14 | - /bin/bash 15 | - -c 16 | - /bin/sleep infinity 17 | command: 18 | - /bin/dumb-init 19 | env: 20 | - name: OS_CLOUD 21 | value: default 22 | image: quay.io/podified-antelope-centos9/openstack-openstackclient:current-podified 23 | imagePullPolicy: IfNotPresent 24 | name: openstackclient 25 | resources: {} 26 | securityContext: 27 | capabilities: 28 | drop: 29 | - ALL 30 | runAsGroup: 42401 31 | runAsNonRoot: true 32 | runAsUser: 42401 33 | allowPrivilegeEscalation: false 34 | seccompProfile: 35 | type: RuntimeDefault 36 | terminationMessagePath: /dev/termination-log 37 | terminationMessagePolicy: File 38 | volumeMounts: 39 | - mountPath: /etc/openstack/clouds.yaml 40 | name: openstack-config 41 | subPath: clouds.yaml 42 | - mountPath: /etc/openstack/secure.yaml 43 | name: openstack-config-secret 44 | subPath: secure.yaml 45 | dnsPolicy: ClusterFirst 46 | enableServiceLinks: true 47 | volumes: 48 | - configMap: 49 | defaultMode: 420 50 | name: openstack-config 51 | name: openstack-config 52 | - name: openstack-config-secret 53 | secret: 54 | defaultMode: 420 55 | secretName: openstack-config-secret 56 | -------------------------------------------------------------------------------- /.github/workflows/release-keystone-operator.yaml: -------------------------------------------------------------------------------- 1 | name: Release Keystone Operator 2 | 3 | on: 4 | release: 5 | types: 6 | - released 7 | - prereleased 8 | 9 | env: 10 | imageregistry: 'quay.io' 11 | imagenamespace: ${{ secrets.IMAGENAMESPACE || secrets.QUAY_USERNAME }} 12 | 13 | jobs: 14 | release: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Tag image 21 | uses: tinact/docker.image-retag@1.0.2 22 | with: 23 | image_name: ${{ env.imagenamespace }}/ 24 | image_old_tag: ${{ github.sha }} 25 | image_new_tag: ${{ github.event.release.tag_name }} 26 | registry: ${{ env.imageregistry }} 27 | registry_username: ${{ secrets.QUAY_USERNAME }} 28 | registry_password: ${{ secrets.QUAY_PASSWORD }} 29 | 30 | - name: Tag -bundle image 31 | uses: tinact/docker.image-retag@1.0.2 32 | with: 33 | image_name: ${{ env.imagenamespace }}/-bundle 34 | image_old_tag: ${{ github.sha }} 35 | image_new_tag: ${{ github.event.release.tag_name }} 36 | registry: ${{ env.imageregistry }} 37 | registry_username: ${{ secrets.QUAY_USERNAME }} 38 | registry_password: ${{ secrets.QUAY_PASSWORD }} 39 | 40 | - name: Tag -index image 41 | uses: tinact/docker.image-retag@1.0.2 42 | with: 43 | image_name: ${{ env.imagenamespace }}/-index 44 | image_old_tag: ${{ github.sha }} 45 | image_new_tag: ${{ github.event.release.tag_name }} 46 | registry: ${{ env.imageregistry }} 47 | registry_username: ${{ secrets.QUAY_USERNAME }} 48 | registry_password: ${{ secrets.QUAY_PASSWORD }} 49 | -------------------------------------------------------------------------------- /internal/keystone/federation.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package keystone 17 | 18 | import ( 19 | "path/filepath" 20 | "strconv" 21 | 22 | corev1 "k8s.io/api/core/v1" 23 | ) 24 | 25 | // getFederationVolumeMounts - get federation mountpoints 26 | func getFederationVolumeMounts( 27 | federationMountPath string, 28 | federationFilenames []string, 29 | ) []corev1.VolumeMount { 30 | vms := []corev1.VolumeMount{} 31 | 32 | for index, filename := range federationFilenames { 33 | vm := corev1.VolumeMount{ 34 | Name: "federation-realm-volume" + strconv.Itoa(index), 35 | MountPath: filepath.Join(federationMountPath, filename), 36 | SubPath: strconv.Itoa(index), 37 | ReadOnly: true, 38 | } 39 | vms = append(vms, vm) 40 | } 41 | 42 | return vms 43 | } 44 | 45 | // getFederationVolumes - get federation volumes 46 | func getFederationVolumes( 47 | federationFilenames []string, 48 | ) []corev1.Volume { 49 | var config0640AccessMode int32 = 0644 50 | vols := []corev1.Volume{} 51 | 52 | for index := range federationFilenames { 53 | vol := corev1.Volume{ 54 | Name: "federation-realm-volume" + strconv.Itoa(index), 55 | VolumeSource: corev1.VolumeSource{ 56 | Secret: &corev1.SecretVolumeSource{ 57 | DefaultMode: &config0640AccessMode, 58 | SecretName: FederationMultiRealmSecret, 59 | }, 60 | }, 61 | } 62 | vols = append(vols, vol) 63 | } 64 | return vols 65 | } 66 | -------------------------------------------------------------------------------- /templates/keystoneapi/config/httpd.conf: -------------------------------------------------------------------------------- 1 | ServerTokens Prod 2 | ServerSignature Off 3 | TraceEnable Off 4 | PidFile run/httpd.pid 5 | ServerRoot "/etc/httpd" 6 | ServerName "localhost.localdomain" 7 | 8 | User apache 9 | Group apache 10 | 11 | Listen 5000 12 | 13 | TypesConfig /etc/mime.types 14 | 15 | Include conf.modules.d/*.conf 16 | Include conf.d/*.conf 17 | 18 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 19 | LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" proxy 20 | 21 | SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded 22 | CustomLog /dev/stdout combined env=!forwarded 23 | CustomLog /dev/stdout proxy env=forwarded 24 | 25 | {{ range $endpt, $vhost := .VHosts }} 26 | # {{ $endpt }} vhost {{ $vhost.ServerName }} configuration 27 | 28 | ServerName {{ $vhost.ServerName }} 29 | TimeOut {{ $.TimeOut }} 30 | 31 | ## Vhost docroot 32 | DocumentRoot "/var/www/cgi-bin/keystone" 33 | 34 | ## Directories, there should at least be a declaration for /var/www/cgi-bin/keystone 35 | 36 | Options -Indexes +FollowSymLinks +MultiViews 37 | AllowOverride None 38 | Require all granted 39 | 40 | 41 | ## Logging 42 | ErrorLog /dev/stdout 43 | ServerSignature Off 44 | CustomLog /dev/stdout combined 45 | 46 | {{- if $vhost.TLS }} 47 | SetEnvIf X-Forwarded-Proto https HTTPS=1 48 | 49 | ## SSL directives 50 | SSLEngine on 51 | SSLCertificateFile "{{ $vhost.SSLCertificateFile }}" 52 | SSLCertificateKeyFile "{{ $vhost.SSLCertificateKeyFile }}" 53 | {{- end }} 54 | 55 | ## WSGI configuration 56 | WSGIApplicationGroup %{GLOBAL} 57 | WSGIDaemonProcess {{ $endpt }} display-name={{ $endpt }} group=keystone processes={{ $.ProcessNumber }} threads=1 user=keystone 58 | WSGIProcessGroup {{ $endpt }} 59 | WSGIScriptAlias / "/usr/bin/keystone-wsgi-public" 60 | WSGIPassAuthorization On 61 | 62 | {{- if $vhost.Override }} 63 | Include conf/httpd_custom_{{ $endpt }}_* 64 | {{- end }} 65 | 66 | 67 | {{ end }} 68 | -------------------------------------------------------------------------------- /internal/keystone/cloudconfig.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package keystone 18 | 19 | import "fmt" 20 | 21 | // OpenStackConfig type 22 | type OpenStackConfig struct { 23 | Clouds struct { 24 | Default struct { 25 | Auth struct { 26 | AuthURL string `yaml:"auth_url"` 27 | ProjectName string `yaml:"project_name"` 28 | UserName string `yaml:"username"` 29 | UserDomainName string `yaml:"user_domain_name"` 30 | ProjectDomainName string `yaml:"project_domain_name"` 31 | } `yaml:"auth"` 32 | RegionName string `yaml:"region_name"` 33 | } `yaml:"default"` 34 | } 35 | } 36 | 37 | // OpenStackConfigSecret type 38 | type OpenStackConfigSecret struct { 39 | Clouds struct { 40 | Default struct { 41 | Auth struct { 42 | Password string `yaml:"password"` 43 | } 44 | } 45 | } 46 | } 47 | 48 | // GenerateCloudrc generates file contents of a cloudrc file for the clients 49 | // until there is parity with openstackclient. 50 | func GenerateCloudrc(secret *OpenStackConfigSecret, config *OpenStackConfig) string { 51 | auth := config.Clouds.Default.Auth 52 | val := fmt.Sprintf( 53 | "export OS_AUTH_URL=%s"+ 54 | "\nexport OS_USERNAME=%s"+ 55 | "\nexport OS_PROJECT_NAME=%s"+ 56 | "\nexport OS_PROJECT_DOMAIN_NAME=%s"+ 57 | "\nexport OS_USER_DOMAIN_NAME=%s"+ 58 | "\nexport OS_PASSWORD=%s", 59 | auth.AuthURL, 60 | auth.UserName, 61 | auth.ProjectName, 62 | auth.ProjectDomainName, 63 | auth.UserDomainName, 64 | secret.Clouds.Default.Auth.Password) 65 | return val 66 | } 67 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: local 3 | hooks: 4 | - id: gotidy 5 | name: gotidy 6 | language: system 7 | entry: make 8 | args: ["tidy"] 9 | pass_filenames: false 10 | - id: make-manifests 11 | name: make-manifests 12 | language: system 13 | entry: make 14 | args: ['manifests'] 15 | pass_filenames: false 16 | - id: make-generate 17 | name: make-generate 18 | language: system 19 | entry: make 20 | args: ['generate'] 21 | pass_filenames: false 22 | - id: make-operator-lint 23 | name: make-operator-lint 24 | language: system 25 | entry: make 26 | args: ['operator-lint'] 27 | pass_filenames: false 28 | - id: make-crd-schema-check 29 | name: make-crd-schema-check 30 | language: system 31 | entry: make 32 | args: ['crd-schema-check'] 33 | pass_filenames: false 34 | 35 | - repo: https://github.com/pre-commit/pre-commit-hooks 36 | rev: v4.4.0 37 | hooks: 38 | - id: check-added-large-files 39 | - id: fix-byte-order-marker 40 | - id: check-case-conflict 41 | - id: check-executables-have-shebangs 42 | exclude: ^vendor 43 | - id: check-shebang-scripts-are-executable 44 | exclude: ^vendor 45 | - id: check-merge-conflict 46 | - id: check-symlinks 47 | - id: destroyed-symlinks 48 | - id: check-yaml 49 | args: [-m] 50 | - id: check-json 51 | - id: detect-private-key 52 | - id: end-of-file-fixer 53 | exclude: ^vendor 54 | - id: no-commit-to-branch 55 | - id: trailing-whitespace 56 | exclude: ^vendor 57 | 58 | - repo: https://github.com/openstack/bashate.git 59 | rev: 2.1.1 60 | hooks: 61 | - id: bashate 62 | entry: bashate --error . --ignore=E006,E040,E011,E020,E012 63 | 64 | - repo: https://github.com/golangci/golangci-lint 65 | rev: v2.4.0 66 | hooks: 67 | - id: golangci-lint-full 68 | args: ["-v"] 69 | 70 | - repo: https://github.com/openstack-k8s-operators/openstack-k8s-operators-ci 71 | # NOTE(gibi): we cannot automatically track main here 72 | # see https://pre-commit.com/#using-the-latest-version-for-a-repository 73 | rev: e30d72fcbced0ab8a7b6d23be1dee129e2a7b849 74 | hooks: 75 | - id: kuttl-single-test-assert 76 | args: ["tests/kuttl"] 77 | -------------------------------------------------------------------------------- /config/manifests/bases/keystone-operator.clusterserviceversion.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: operators.coreos.com/v1alpha1 2 | kind: ClusterServiceVersion 3 | metadata: 4 | annotations: 5 | alm-examples: '[]' 6 | capabilities: Basic Install 7 | features.operators.openshift.io/disconnected: "true" 8 | features.operators.openshift.io/fips-compliant: "true" 9 | features.operators.openshift.io/proxy-aware: "false" 10 | features.operators.openshift.io/tls-profiles: "false" 11 | features.operators.openshift.io/token-auth-aws: "false" 12 | features.operators.openshift.io/token-auth-azure: "false" 13 | features.operators.openshift.io/token-auth-gcp: "false" 14 | operatorframework.io/suggested-namespace: openstack 15 | operators.operatorframework.io/operator-type: non-standalone 16 | name: keystone-operator.v0.0.0 17 | namespace: placeholder 18 | spec: 19 | apiservicedefinitions: {} 20 | customresourcedefinitions: 21 | owned: 22 | - description: KeystoneAPI is the Schema for the keystoneapis API 23 | displayName: Keystone API 24 | kind: KeystoneAPI 25 | name: keystoneapis.keystone.openstack.org 26 | specDescriptors: 27 | - description: TLS - Parameters related to the TLS 28 | displayName: TLS 29 | path: tls 30 | version: v1beta1 31 | - description: KeystoneEndpoint is the Schema for the keystoneendpoints API 32 | displayName: Keystone Endpoint 33 | kind: KeystoneEndpoint 34 | name: keystoneendpoints.keystone.openstack.org 35 | version: v1beta1 36 | - description: KeystoneService is the Schema for the keystoneservices API 37 | displayName: Keystone Service 38 | kind: KeystoneService 39 | name: keystoneservices.keystone.openstack.org 40 | version: v1beta1 41 | description: Keystone Operator 42 | displayName: Keystone Operator 43 | install: 44 | spec: 45 | deployments: null 46 | strategy: "" 47 | installModes: 48 | - supported: true 49 | type: OwnNamespace 50 | - supported: true 51 | type: SingleNamespace 52 | - supported: false 53 | type: MultiNamespace 54 | - supported: true 55 | type: AllNamespaces 56 | keywords: 57 | - OpenStack 58 | - Identity 59 | - Keystone 60 | links: 61 | - name: Keystone Operator 62 | url: https://github.com/openstack-k8s-operators/keystone-operator 63 | maturity: beta 64 | provider: 65 | name: Red Hat Inc. 66 | url: https://redhat.com/ 67 | version: 0.0.0 68 | minKubeVersion: 0.0.0 69 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG GOLANG_BUILDER=registry.access.redhat.com/ubi9/go-toolset:1.24 2 | ARG OPERATOR_BASE_IMAGE=registry.access.redhat.com/ubi9/ubi-minimal:latest 3 | 4 | # Build the manager binary 5 | FROM $GOLANG_BUILDER AS builder 6 | 7 | #Arguments required by OSBS build system 8 | ARG CACHITO_ENV_FILE=/remote-source/cachito.env 9 | 10 | ARG REMOTE_SOURCE=. 11 | ARG REMOTE_SOURCE_DIR=/remote-source 12 | ARG REMOTE_SOURCE_SUBDIR= 13 | ARG DEST_ROOT=/dest-root 14 | 15 | ARG GO_BUILD_EXTRA_ARGS="-tags strictfipsruntime" 16 | ARG GO_BUILD_EXTRA_ENV_ARGS="CGO_ENABLED=1 GO111MODULE=on" 17 | 18 | COPY $REMOTE_SOURCE $REMOTE_SOURCE_DIR 19 | WORKDIR $REMOTE_SOURCE_DIR/$REMOTE_SOURCE_SUBDIR 20 | 21 | USER root 22 | RUN mkdir -p ${DEST_ROOT}/usr/local/bin/ 23 | 24 | # cache deps before building and copying source so that we don't need to re-download as much 25 | # and so that source changes don't invalidate our downloaded layer 26 | RUN if [ ! -f $CACHITO_ENV_FILE ]; then go mod download ; fi 27 | 28 | # Build manager 29 | RUN if [ -f $CACHITO_ENV_FILE ] ; then source $CACHITO_ENV_FILE ; fi ; env ${GO_BUILD_EXTRA_ENV_ARGS} go build ${GO_BUILD_EXTRA_ARGS} -a -o ${DEST_ROOT}/manager cmd/main.go 30 | 31 | RUN cp -r templates ${DEST_ROOT}/templates 32 | 33 | # Use distroless as minimal base image to package the manager binary 34 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 35 | FROM $OPERATOR_BASE_IMAGE 36 | 37 | ARG DEST_ROOT=/dest-root 38 | # NONROOT default id https://github.com/GoogleContainerTools/distroless/blob/main/base/base.bzl#L8= 39 | ARG USER_ID=65532 40 | 41 | ARG IMAGE_COMPONENT="keystone-operator-container" 42 | ARG IMAGE_NAME="keystone-operator" 43 | ARG IMAGE_VERSION="1.0.0" 44 | ARG IMAGE_SUMMARY="Keystone Operator" 45 | ARG IMAGE_DESC="This image includes the keystone-operator" 46 | ARG IMAGE_TAGS="cn-openstack openstack" 47 | 48 | ### DO NOT EDIT LINES BELOW 49 | # Auto generated using CI tools from 50 | # https://github.com/openstack-k8s-operators/openstack-k8s-operators-ci 51 | 52 | # Labels required by upstream and osbs build system 53 | LABEL com.redhat.component="${IMAGE_COMPONENT}" \ 54 | name="${IMAGE_NAME}" \ 55 | version="${IMAGE_VERSION}" \ 56 | summary="${IMAGE_SUMMARY}" \ 57 | io.k8s.name="${IMAGE_NAME}" \ 58 | io.k8s.description="${IMAGE_DESC}" \ 59 | io.openshift.tags="${IMAGE_TAGS}" 60 | ### DO NOT EDIT LINES ABOVE 61 | 62 | ENV USER_UID=$USER_ID \ 63 | OPERATOR_TEMPLATES=/usr/share/keystone-operator/templates/ \ 64 | WATCH_NAMESPACE=openstack,openshift-machine-api,openshift-sriov-network-operator 65 | 66 | WORKDIR / 67 | 68 | # Install operator binary to WORKDIR 69 | COPY --from=builder ${DEST_ROOT}/manager . 70 | 71 | # Install templates 72 | COPY --from=builder ${DEST_ROOT}/templates ${OPERATOR_TEMPLATES} 73 | 74 | USER $USER_ID 75 | 76 | ENV PATH="/:${PATH}" 77 | 78 | ENTRYPOINT ["/manager"] 79 | -------------------------------------------------------------------------------- /internal/keystone/const.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package keystone 17 | 18 | import ( 19 | "github.com/openstack-k8s-operators/lib-common/modules/storage" 20 | ) 21 | 22 | const ( 23 | // ServiceName - 24 | ServiceName = "keystone" 25 | // DatabaseName - 26 | DatabaseName = "keystone" 27 | // DatabaseCRName - 28 | DatabaseCRName = "keystone" 29 | // DatabaseUsernamePrefix - 30 | DatabaseUsernamePrefix = "keystone" 31 | // KeystonePublicPort - 32 | KeystonePublicPort int32 = 5000 33 | // KeystoneInternalPort - 34 | KeystoneInternalPort int32 = 5000 35 | // KeystoneUID is based on kolla 36 | // https://github.com/openstack/kolla/blob/master/kolla/common/users.py 37 | KeystoneUID int64 = 42425 38 | // DefaultFernetMaxActiveKeys - 39 | DefaultFernetMaxActiveKeys = 5 40 | // DefaultFernetRotationDays - 41 | DefaultFernetRotationDays = 1 42 | // DBSyncCommand - 43 | DBSyncCommand = "keystone-manage db_sync" 44 | // Keystone is the global ServiceType 45 | Keystone storage.PropagationType = "Keystone" 46 | // KeystoneCronJob is the CronJob ServiceType 47 | KeystoneCronJob storage.PropagationType = "KeystoneCron" 48 | // KeystoneBootstrap is the bootstrap service 49 | KeystoneBootstrap storage.PropagationType = "KeystoneBootstrap" 50 | // FederationConfigKey - key for multi-realm federation config secret 51 | FederationConfigKey = "federation-config.json" 52 | // FederationMultiRealmSecret - secret to store processed multirealm data 53 | FederationMultiRealmSecret = "keystone-multirealm-federation-secret" 54 | // FederationDefaultMountPath - if user doesn't specify otherwise, this location is used 55 | FederationDefaultMountPath = "/var/lib/config-data/default/multirealm-federation" 56 | ) 57 | 58 | // KeystonePropagation is the definition of the Keystone propagation service 59 | var KeystonePropagation = []storage.PropagationType{Keystone} 60 | 61 | // DBSyncPropagation keeps track of the DBSync Service Propagation Type 62 | var DBSyncPropagation = []storage.PropagationType{storage.DBSync} 63 | 64 | // BootstrapPropagation keeps track of the KeystoneBootstrap Propagation Type 65 | var BootstrapPropagation = []storage.PropagationType{KeystoneBootstrap} 66 | 67 | // KeystoneCronJobPropagation keeps track of the KeystoneBootstrap Propagation Type 68 | var KeystoneCronJobPropagation = []storage.PropagationType{KeystoneCronJob} 69 | -------------------------------------------------------------------------------- /internal/keystone/dbsync.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package keystone 17 | 18 | import ( 19 | keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" 20 | 21 | "github.com/openstack-k8s-operators/lib-common/modules/common/env" 22 | 23 | batchv1 "k8s.io/api/batch/v1" 24 | corev1 "k8s.io/api/core/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | ) 27 | 28 | // DbSyncJob func 29 | func DbSyncJob( 30 | instance *keystonev1.KeystoneAPI, 31 | labels map[string]string, 32 | annotations map[string]string, 33 | ) *batchv1.Job { 34 | 35 | args := []string{"-c", DBSyncCommand} 36 | 37 | envVars := map[string]env.Setter{} 38 | envVars["KOLLA_CONFIG_STRATEGY"] = env.SetValue("COPY_ALWAYS") 39 | envVars["KOLLA_BOOTSTRAP"] = env.SetValue("true") 40 | 41 | // create Volume and VolumeMounts 42 | dbSyncExtraMounts := []keystonev1.KeystoneExtraMounts{} 43 | volumes := getVolumes(instance, dbSyncExtraMounts, DBSyncPropagation) 44 | volumeMounts := getDBSyncVolumeMounts() 45 | 46 | // add CA cert if defined 47 | if instance.Spec.TLS.CaBundleSecretName != "" { 48 | volumes = append(volumes, instance.Spec.TLS.CreateVolume()) 49 | volumeMounts = append(volumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) 50 | } 51 | 52 | job := &batchv1.Job{ 53 | ObjectMeta: metav1.ObjectMeta{ 54 | Name: ServiceName + "-db-sync", 55 | Namespace: instance.Namespace, 56 | Labels: labels, 57 | }, 58 | Spec: batchv1.JobSpec{ 59 | Template: corev1.PodTemplateSpec{ 60 | ObjectMeta: metav1.ObjectMeta{ 61 | Annotations: annotations, 62 | }, 63 | Spec: corev1.PodSpec{ 64 | RestartPolicy: corev1.RestartPolicyOnFailure, 65 | ServiceAccountName: instance.RbacResourceName(), 66 | Containers: []corev1.Container{ 67 | { 68 | Name: ServiceName + "-db-sync", 69 | Command: []string{ 70 | "/bin/bash", 71 | }, 72 | Args: args, 73 | Image: instance.Spec.ContainerImage, 74 | SecurityContext: dbSyncSecurityContext(), 75 | Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), 76 | VolumeMounts: volumeMounts, 77 | }, 78 | }, 79 | Volumes: volumes, 80 | }, 81 | }, 82 | }, 83 | } 84 | 85 | if instance.Spec.NodeSelector != nil { 86 | job.Spec.Template.Spec.NodeSelector = *instance.Spec.NodeSelector 87 | } 88 | 89 | return job 90 | } 91 | -------------------------------------------------------------------------------- /test/kuttl/common/tls_ca_bundle.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Internal CA created with: 3 | # 4 | # apiVersion: cert-manager.io/v1 5 | # kind: Issuer 6 | # metadata: 7 | # name: rootca-kuttl-internal 8 | # namespace: openstack 9 | # spec: 10 | # ca: 11 | # secretName: rootca-kuttl-internal 12 | # 13 | # apiVersion: cert-manager.io/v1 14 | # kind: Certificate 15 | # metadata: 16 | # name: rootca-kuttl-internal 17 | # namespace: openstack 18 | # spec: 19 | # commonName: rootca-kuttl-internal 20 | # duration: 87600h0m0s 21 | # isCA: true 22 | # issuerRef: 23 | # name: selfsigned-issuer 24 | # privateKey: 25 | # algorithm: ECDSA 26 | # size: 256 27 | # secretName: rootca-kuttl-internal 28 | # 29 | # External CA created with: 30 | # 31 | # apiVersion: cert-manager.io/v1 32 | # kind: Issuer 33 | # metadata: 34 | # name: rootca-kuttl-public 35 | # namespace: openstack 36 | # spec: 37 | # ca: 38 | # secretName: rootca-kuttl-public 39 | # 40 | # apiVersion: cert-manager.io/v1 41 | # kind: Certificate 42 | # metadata: 43 | # name: rootca-kuttl-public 44 | # namespace: openstack 45 | # spec: 46 | # commonName: rootca-kuttl-public 47 | # duration: 87600h0m0s 48 | # isCA: true 49 | # issuerRef: 50 | # name: selfsigned-issuer 51 | # privateKey: 52 | # algorithm: ECDSA 53 | # size: 256 54 | # secretName: rootca-kuttl-public 55 | # 56 | # Then extracted both CAs and created added them as the bundle: 57 | apiVersion: v1 58 | data: 59 | tls-ca-bundle.pem: IyByb290Y2EtaW50ZXJuYWwKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJmekNDQVNhZ0F3SUJBZ0lRUWxlcTNZcDBtU2kwVDNiTm03Q29UVEFLQmdncWhrak9QUVFEQWpBZ01SNHcKSEFZRFZRUURFeFZ5YjI5MFkyRXRhM1YwZEd3dGFXNTBaWEp1WVd3d0hoY05NalF3TVRFMU1URTBOelUwV2hjTgpNelF3TVRFeU1URTBOelUwV2pBZ01SNHdIQVlEVlFRREV4VnliMjkwWTJFdGEzVjBkR3d0YVc1MFpYSnVZV3d3CldUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTRk9rNHJPUldVUGhoTjUrK09EN1I2MW5Gb1lBY0QKenpvUS91SW93NktjeGhwRWNQTDFxb3ZZUGxUYUJabEh3c2FpNE50VHA4aDA1RHVRSGZKOE9JNXFvMEl3UURBTwpCZ05WSFE4QkFmOEVCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVXE3TGtFSk1TCm1MOVpKWjBSOUluKzZkclhycEl3Q2dZSUtvWkl6ajBFQXdJRFJ3QXdSQUlnVlN1K00ydnZ3QlF3eTJHMVlhdkkKQld2RGtSNlRla0I5U0VqdzJIblRSMWtDSUZSNFNkWGFPQkFGWjVHa2RLWCtSY2IzaDFIZm52eFJEVW96bTl2agphenp3Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KIyByb290Y2EtcHVibGljCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlCZXpDQ0FTS2dBd0lCQWdJUU5IREdZc0JzNzk4aWJERDdxL28ybGpBS0JnZ3Foa2pPUFFRREFqQWVNUnd3CkdnWURWUVFERXhOeWIyOTBZMkV0YTNWMGRHd3RjSFZpYkdsak1CNFhEVEkwTURFeE5URXdNVFV6TmxvWERUTTAKTURFeE1qRXdNVFV6Tmxvd0hqRWNNQm9HQTFVRUF4TVRjbTl2ZEdOaExXdDFkSFJzTFhCMVlteHBZekJaTUJNRwpCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQkQ3OGF2WHFocmhDNXc4czlXa2Q0SXBiZUV1MDNDUitYWFVkCmtEek9SeXhhOXdjY0lkRGl2YkdKakpGWlRUY1ZtYmpxMUJNWXNqcjEyVUlFNUVUM1ZscWpRakJBTUE0R0ExVWQKRHdFQi93UUVBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJUS0ppeldVSjllVUtpMQpkczBscjZjNnNEN0VCREFLQmdncWhrak9QUVFEQWdOSEFEQkVBaUJJWndZcTYxQnFNSmFCNlVjRm9Sc3hlY3dICjV6L3pNT2RyT3llMG1OaThKZ0lnUUxCNHdES3JwZjl0WDJsb00rMHVUb3BBRFNZSW5yY2ZWdTRGQnVZVTNJZz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= 60 | kind: Secret 61 | metadata: 62 | labels: 63 | combined-ca-bundle: "" 64 | name: combined-ca-bundle 65 | type: Opaque 66 | -------------------------------------------------------------------------------- /api/v1beta1/keystoneservice_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | import ( 20 | "github.com/openstack-k8s-operators/lib-common/modules/common/condition" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // KeystoneServiceSpec defines the desired state of KeystoneService 25 | type KeystoneServiceSpec struct { 26 | // +kubebuilder:validation:Required 27 | // ServiceType - Type is the type of the service. 28 | ServiceType string `json:"serviceType"` 29 | // +kubebuilder:validation:Required 30 | // ServiceName - Name of the service. 31 | ServiceName string `json:"serviceName"` 32 | // +kubebuilder:validation:Optional 33 | // ServiceDescription - Description for the service. 34 | ServiceDescription string `json:"serviceDescription,omitempty"` 35 | // +kubebuilder:validation:Required 36 | // Enabled - whether or not the service is enabled. 37 | Enabled bool `json:"enabled"` 38 | // +kubebuilder:validation:Required 39 | // ServiceUser - optional username used for this service 40 | ServiceUser string `json:"serviceUser"` 41 | // +kubebuilder:validation:Required 42 | // Secret containing OpenStack password information for the ServiceUser 43 | Secret string `json:"secret"` 44 | // +kubebuilder:validation:Required 45 | // PasswordSelector - Selector to get the ServiceUser password from the Secret, e.g. PlacementPassword 46 | PasswordSelector string `json:"passwordSelector"` 47 | } 48 | 49 | // KeystoneServiceStatus defines the observed state of KeystoneService 50 | type KeystoneServiceStatus struct { 51 | ServiceID string `json:"serviceID,omitempty"` 52 | // Conditions 53 | Conditions condition.Conditions `json:"conditions,omitempty" optional:"true"` 54 | } 55 | 56 | //+kubebuilder:object:root=true 57 | //+kubebuilder:subresource:status 58 | //+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[0].status",description="Status" 59 | //+kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[0].message",description="Message" 60 | 61 | // KeystoneService is the Schema for the keystoneservices API 62 | type KeystoneService struct { 63 | metav1.TypeMeta `json:",inline"` 64 | metav1.ObjectMeta `json:"metadata,omitempty"` 65 | 66 | Spec KeystoneServiceSpec `json:"spec,omitempty"` 67 | Status KeystoneServiceStatus `json:"status,omitempty"` 68 | } 69 | 70 | //+kubebuilder:object:root=true 71 | 72 | // KeystoneServiceList contains a list of KeystoneService 73 | type KeystoneServiceList struct { 74 | metav1.TypeMeta `json:",inline"` 75 | metav1.ListMeta `json:"metadata,omitempty"` 76 | Items []KeystoneService `json:"items"` 77 | } 78 | 79 | func init() { 80 | SchemeBuilder.Register(&KeystoneService{}, &KeystoneServiceList{}) 81 | } 82 | 83 | // IsReady - returns true if KeystoneService is reconciled successfully 84 | func (instance KeystoneService) IsReady() bool { 85 | return instance.Status.Conditions.IsTrue(condition.ReadyCondition) 86 | } 87 | -------------------------------------------------------------------------------- /templates/keystoneapi/config/keystone-api-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": "/usr/sbin/httpd", 3 | "config_files": [ 4 | { 5 | "source": "/var/lib/config-data/default/keystone.conf", 6 | "dest": "/etc/keystone/keystone.conf", 7 | "owner": "keystone", 8 | "perm": "0600" 9 | }, 10 | { 11 | "source": "/var/lib/config-data/default/custom.conf", 12 | "dest": "/etc/keystone/keystone.conf.d/custom.conf", 13 | "owner": "keystone", 14 | "perm": "0600" 15 | }, 16 | { 17 | "source": "/var/lib/config-data/default/httpd.conf", 18 | "dest": "/etc/httpd/conf/httpd.conf", 19 | "owner": "keystone:apache", 20 | "perm": "0644" 21 | }, 22 | { 23 | "source": "/var/lib/config-data/default/ssl.conf", 24 | "dest": "/etc/httpd/conf.d/ssl.conf", 25 | "owner": "keystone:apache", 26 | "perm": "0644" 27 | }, 28 | { 29 | "source": "/var/lib/config-data/tls/certs/*", 30 | "dest": "/etc/pki/tls/certs/", 31 | "owner": "keystone:apache", 32 | "perm": "0640", 33 | "optional": true, 34 | "merge": true 35 | }, 36 | { 37 | "source": "/var/lib/config-data/tls/private/*", 38 | "dest": "/etc/pki/tls/private/", 39 | "owner": "keystone:apache", 40 | "perm": "0600", 41 | "optional": true, 42 | "merge": true 43 | }, 44 | { 45 | "source": "/var/lib/config-data/mtls/certs/*", 46 | "dest": "/etc/pki/tls/certs/", 47 | "owner": "root:keystone", 48 | "perm": "0640", 49 | "optional": true, 50 | "merge": true 51 | }, 52 | { 53 | "source": "/var/lib/config-data/mtls/private/*", 54 | "dest": "/etc/pki/tls/private/", 55 | "owner": "root:keystone", 56 | "perm": "0640", 57 | "optional": true, 58 | "merge": true 59 | }, 60 | { 61 | "source": "/var/lib/config-data/default/my.cnf", 62 | "dest": "/etc/my.cnf", 63 | "owner": "keystone", 64 | "perm": "0644" 65 | }, 66 | { 67 | "source": "/var/lib/config-data/default/httpd_custom_*", 68 | "dest": "/etc/httpd/conf/", 69 | "owner": "keystone:apache", 70 | "perm": "0444", 71 | "optional": true 72 | }, 73 | { 74 | "source": "/var/lib/config-data/default/multirealm-federation/*", 75 | "dest": "/var/lib/httpd/metadata/", 76 | "owner": "keystone:apache", 77 | "perm": "0640", 78 | "optional": true, 79 | "merge": true 80 | } 81 | 82 | ], 83 | "permissions": [ 84 | { 85 | "path": "/etc/httpd", 86 | "owner": "keystone:apache", 87 | "recurse": true 88 | }, 89 | { 90 | "path": "/var/log/keystone", 91 | "owner": "keystone:apache", 92 | "recurse": true 93 | }, 94 | { 95 | "path": "/var/lib/httpd/metadata", 96 | "owner": "keystone:apache", 97 | "perm": "0775", 98 | "recurse": true 99 | } 100 | ] 101 | } 102 | -------------------------------------------------------------------------------- /api/v1beta1/keystoneendpoint_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | import ( 20 | "github.com/openstack-k8s-operators/lib-common/modules/common/condition" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // KeystoneEndpointSpec defines the desired state of KeystoneEndpoint 25 | type KeystoneEndpointSpec struct { 26 | // +kubebuilder:validation:Required 27 | // ServiceName - Name of the service to create the endpoint for 28 | ServiceName string `json:"serviceName"` 29 | // +kubebuilder:validation:Required 30 | // Endpoints - map with service api endpoint URLs with the endpoint type as index 31 | Endpoints map[string]string `json:"endpoints"` 32 | } 33 | 34 | // KeystoneEndpointStatus defines the observed state of KeystoneEndpoint 35 | type KeystoneEndpointStatus struct { 36 | EndpointIDs map[string]string `json:"endpointIDs,omitempty"` 37 | ServiceID string `json:"serviceID,omitempty"` 38 | // Conditions 39 | Conditions condition.Conditions `json:"conditions,omitempty" optional:"true"` 40 | 41 | // Endpoints - current status of latest configured endpoints for the service 42 | Endpoints []Endpoint `json:"endpoints,omitempty"` 43 | 44 | //ObservedGeneration - the most recent generation observed for this service. If the observed generation is less than the spec generation, then the controller has not processed the latest changes. 45 | ObservedGeneration int64 `json:"observedGeneration,omitempty"` 46 | } 47 | 48 | // Endpoint - 49 | type Endpoint struct { 50 | // Interface - public, internal, admin 51 | Interface string `json:"interface"` 52 | // URL - endpoint url 53 | URL string `json:"url"` 54 | // ID - endpoint id 55 | ID string `json:"id"` 56 | } 57 | 58 | //+kubebuilder:object:root=true 59 | //+kubebuilder:subresource:status 60 | //+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[0].status",description="Status" 61 | //+kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[0].message",description="Message" 62 | 63 | // KeystoneEndpoint is the Schema for the keystoneendpoints API 64 | type KeystoneEndpoint struct { 65 | metav1.TypeMeta `json:",inline"` 66 | metav1.ObjectMeta `json:"metadata,omitempty"` 67 | 68 | Spec KeystoneEndpointSpec `json:"spec,omitempty"` 69 | Status KeystoneEndpointStatus `json:"status,omitempty"` 70 | } 71 | 72 | //+kubebuilder:object:root=true 73 | 74 | // KeystoneEndpointList contains a list of KeystoneEndpoint 75 | type KeystoneEndpointList struct { 76 | metav1.TypeMeta `json:",inline"` 77 | metav1.ListMeta `json:"metadata,omitempty"` 78 | Items []KeystoneEndpoint `json:"items"` 79 | } 80 | 81 | func init() { 82 | SchemeBuilder.Register(&KeystoneEndpoint{}, &KeystoneEndpointList{}) 83 | } 84 | 85 | // IsReady - returns true if KeystoneEndpoint is reconciled successfully 86 | func (instance KeystoneEndpoint) IsReady() bool { 87 | return instance.Status.Conditions.IsTrue(condition.ReadyCondition) 88 | } 89 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | - pods 12 | - secrets 13 | - services 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - "" 24 | resources: 25 | - serviceaccounts 26 | verbs: 27 | - create 28 | - get 29 | - list 30 | - patch 31 | - update 32 | - watch 33 | - apiGroups: 34 | - apps 35 | resources: 36 | - deployments 37 | verbs: 38 | - create 39 | - delete 40 | - get 41 | - list 42 | - patch 43 | - update 44 | - watch 45 | - apiGroups: 46 | - batch 47 | resources: 48 | - cronjobs 49 | - jobs 50 | verbs: 51 | - create 52 | - delete 53 | - get 54 | - list 55 | - patch 56 | - update 57 | - watch 58 | - apiGroups: 59 | - k8s.cni.cncf.io 60 | resources: 61 | - network-attachment-definitions 62 | verbs: 63 | - get 64 | - list 65 | - watch 66 | - apiGroups: 67 | - keystone.openstack.org 68 | resources: 69 | - keystoneapis 70 | - keystoneendpoints 71 | - keystoneservices 72 | verbs: 73 | - create 74 | - delete 75 | - get 76 | - list 77 | - patch 78 | - update 79 | - watch 80 | - apiGroups: 81 | - keystone.openstack.org 82 | resources: 83 | - keystoneapis/finalizers 84 | - keystoneendpoints/finalizers 85 | - keystoneservices/finalizers 86 | verbs: 87 | - patch 88 | - update 89 | - apiGroups: 90 | - keystone.openstack.org 91 | resources: 92 | - keystoneapis/status 93 | - keystoneendpoints/status 94 | - keystoneservices/status 95 | verbs: 96 | - get 97 | - patch 98 | - update 99 | - apiGroups: 100 | - mariadb.openstack.org 101 | resources: 102 | - mariadbaccounts 103 | - mariadbdatabases 104 | verbs: 105 | - create 106 | - delete 107 | - get 108 | - list 109 | - patch 110 | - update 111 | - watch 112 | - apiGroups: 113 | - mariadb.openstack.org 114 | resources: 115 | - mariadbaccounts/finalizers 116 | verbs: 117 | - patch 118 | - update 119 | - apiGroups: 120 | - memcached.openstack.org 121 | resources: 122 | - memcacheds 123 | verbs: 124 | - get 125 | - list 126 | - patch 127 | - update 128 | - watch 129 | - apiGroups: 130 | - memcached.openstack.org 131 | resources: 132 | - memcacheds/finalizers 133 | verbs: 134 | - patch 135 | - update 136 | - apiGroups: 137 | - rabbitmq.openstack.org 138 | resources: 139 | - transporturls 140 | verbs: 141 | - create 142 | - delete 143 | - get 144 | - list 145 | - patch 146 | - update 147 | - watch 148 | - apiGroups: 149 | - rbac.authorization.k8s.io 150 | resources: 151 | - rolebindings 152 | - roles 153 | verbs: 154 | - create 155 | - get 156 | - list 157 | - patch 158 | - update 159 | - watch 160 | - apiGroups: 161 | - route.openshift.io 162 | resources: 163 | - routes 164 | verbs: 165 | - create 166 | - delete 167 | - get 168 | - list 169 | - patch 170 | - update 171 | - watch 172 | - apiGroups: 173 | - security.openshift.io 174 | resourceNames: 175 | - anyuid 176 | resources: 177 | - securitycontextconstraints 178 | verbs: 179 | - use 180 | - apiGroups: 181 | - topology.openstack.org 182 | resources: 183 | - topologies 184 | verbs: 185 | - get 186 | - list 187 | - update 188 | - watch 189 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: keystone-operator 7 | app.kubernetes.io/managed-by: kustomize 8 | name: system 9 | --- 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | metadata: 13 | name: controller-manager 14 | namespace: system 15 | labels: 16 | control-plane: controller-manager 17 | app.kubernetes.io/name: keystone-operator 18 | app.kubernetes.io/managed-by: kustomize 19 | spec: 20 | selector: 21 | matchLabels: 22 | control-plane: controller-manager 23 | app.kubernetes.io/name: keystone-operator 24 | replicas: 1 25 | template: 26 | metadata: 27 | annotations: 28 | kubectl.kubernetes.io/default-container: manager 29 | labels: 30 | control-plane: controller-manager 31 | app.kubernetes.io/name: keystone-operator 32 | spec: 33 | # TODO(user): Uncomment the following code to configure the nodeAffinity expression 34 | # according to the platforms which are supported by your solution. 35 | # It is considered best practice to support multiple architectures. You can 36 | # build your manager image using the makefile target docker-buildx. 37 | # affinity: 38 | # nodeAffinity: 39 | # requiredDuringSchedulingIgnoredDuringExecution: 40 | # nodeSelectorTerms: 41 | # - matchExpressions: 42 | # - key: kubernetes.io/arch 43 | # operator: In 44 | # values: 45 | # - amd64 46 | # - arm64 47 | # - ppc64le 48 | # - s390x 49 | # - key: kubernetes.io/os 50 | # operator: In 51 | # values: 52 | # - linux 53 | securityContext: 54 | # Projects are configured by default to adhere to the "restricted" Pod Security Standards. 55 | # This ensures that deployments meet the highest security requirements for Kubernetes. 56 | # For more details, see: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted 57 | runAsNonRoot: true 58 | seccompProfile: 59 | type: RuntimeDefault 60 | containers: 61 | - command: 62 | - /manager 63 | args: 64 | - --leader-elect 65 | - --health-probe-bind-address=:8081 66 | image: controller:latest 67 | name: manager 68 | ports: [] 69 | securityContext: 70 | allowPrivilegeEscalation: false 71 | capabilities: 72 | drop: 73 | - "ALL" 74 | livenessProbe: 75 | httpGet: 76 | path: /healthz 77 | port: 8081 78 | initialDelaySeconds: 15 79 | periodSeconds: 20 80 | readinessProbe: 81 | httpGet: 82 | path: /readyz 83 | port: 8081 84 | initialDelaySeconds: 5 85 | periodSeconds: 10 86 | # TODO(user): Configure the resources accordingly based on the project requirements. 87 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 88 | resources: 89 | limits: 90 | cpu: 500m 91 | memory: 128Mi 92 | requests: 93 | cpu: 10m 94 | memory: 64Mi 95 | volumeMounts: [] 96 | volumes: [] 97 | serviceAccountName: controller-manager 98 | terminationGracePeriodSeconds: 10 99 | -------------------------------------------------------------------------------- /api/v1beta1/conditions.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package v1beta1 17 | 18 | import ( 19 | condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" 20 | ) 21 | 22 | // Keystone Condition Types used by API objects. 23 | const ( 24 | // KeystoneAPIReadyCondition Status=True condition which indicates if the KeystoneAPI is configured and operational 25 | KeystoneAPIReadyCondition condition.Type = "KeystoneAPIReady" 26 | 27 | // AdminServiceClientReadyCondition Status=True condition which indicates if the admin client is ready and can be used 28 | AdminServiceClientReadyCondition condition.Type = "AdminServiceClientReady" 29 | 30 | // KeystoneServiceOSServiceReadyCondition Status=True condition which indicates if the service created in the keystone instance is ready/was successful 31 | KeystoneServiceOSServiceReadyCondition condition.Type = "KeystoneServiceOSServiceReady" 32 | 33 | // KeystoneServiceOSEndpointsReadyCondition Status=True condition which indicates if the endpoints got created in the keystone instance is ready/was successful 34 | KeystoneServiceOSEndpointsReadyCondition condition.Type = "KeystoneServiceOSEndpointsReady" 35 | 36 | // KeystoneServiceOSUserReadyCondition Status=True condition which indicates if the service user got created in the keystone instance is ready/was successful 37 | KeystoneServiceOSUserReadyCondition condition.Type = "KeystoneServiceOSUserReady" 38 | ) 39 | 40 | // Common Messages used by API objects. 41 | const ( 42 | 43 | // 44 | // KeystoneAPIReady condition messages 45 | // 46 | // KeystoneAPIReadyInitMessage 47 | KeystoneAPIReadyInitMessage = "KeystoneAPI not started" 48 | 49 | // KeystoneAPIReadyMessage 50 | KeystoneAPIReadyMessage = "KeystoneAPI ready" 51 | 52 | // KeystoneAPIReadyNotFoundMessage 53 | KeystoneAPIReadyNotFoundMessage = "KeystoneAPI not found" 54 | 55 | // KeystoneAPIReadyWaitingMessage 56 | KeystoneAPIReadyWaitingMessage = "KeystoneAPI not yet ready" 57 | 58 | // KeystoneAPIReadyErrorMessage 59 | KeystoneAPIReadyErrorMessage = "KeystoneAPI error occured %s" 60 | 61 | // 62 | // AdminServiceClientReady condition messages 63 | // 64 | // AdminServiceClientReadyInitMessage 65 | AdminServiceClientReadyInitMessage = "Admin client not started" 66 | 67 | // AdminServiceClientReadyMessage 68 | AdminServiceClientReadyMessage = "Admin client ready" 69 | 70 | // AdminServiceClientReadyWaitingMessage 71 | AdminServiceClientReadyWaitingMessage = "Admin client not yet ready" 72 | 73 | // AdminServiceClientReadyErrorMessage 74 | AdminServiceClientReadyErrorMessage = "Admin client error occured %s" 75 | 76 | // 77 | // KeystoneServiceOSServiceReady condition messages 78 | // 79 | // KeystoneServiceOSServiceReadyInitMessage 80 | KeystoneServiceOSServiceReadyInitMessage = "Keystone Service registration not started" 81 | 82 | // KeystoneServiceOSServiceReadyMessage 83 | KeystoneServiceOSServiceReadyMessage = "Keystone Service %s - %s ready" 84 | 85 | // AdminServiceClientReadyErrorMessage 86 | KeystoneServiceOSServiceReadyErrorMessage = "Keystone Service error occured %s" 87 | 88 | // 89 | // KeystoneServiceOSEndpointsReady condition messages 90 | // 91 | // KeystoneServiceOSEndpointsReadyInitMessage 92 | KeystoneServiceOSEndpointsReadyInitMessage = "Keystone Endpoints registration not started" 93 | 94 | // KeystoneServiceOSEndpointsReadyMessage 95 | KeystoneServiceOSEndpointsReadyMessage = "Keystone Endpoints ready: %+v" 96 | 97 | // KeystoneServiceOSEndpointsReadyErrorMessage 98 | KeystoneServiceOSEndpointsReadyErrorMessage = "Keystone Endpoints error occured %s" 99 | 100 | // 101 | // KeystoneServiceOSUserReady condition messages 102 | // 103 | // KeystoneServiceOSUserReadyInitMessage 104 | KeystoneServiceOSUserReadyInitMessage = "Keystone Service user registration not started" 105 | 106 | // KeystoneServiceOSUserReadyMessage 107 | KeystoneServiceOSUserReadyMessage = "Keystone Service user %s ready" 108 | 109 | // KeystoneServiceOSUserReadyWaitingMessage 110 | KeystoneServiceOSUserReadyWaitingMessage = "Keystone Service user not yet ready" 111 | 112 | // KeystoneServiceOSUserReadyErrorMessage 113 | KeystoneServiceOSUserReadyErrorMessage = "Keystone Service user error occured %s" 114 | ) 115 | -------------------------------------------------------------------------------- /internal/keystone/cronjob.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package keystone 17 | 18 | import ( 19 | memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" 20 | keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" 21 | "github.com/openstack-k8s-operators/lib-common/modules/common/env" 22 | "github.com/openstack-k8s-operators/lib-common/modules/common/tls" 23 | 24 | batchv1 "k8s.io/api/batch/v1" 25 | corev1 "k8s.io/api/core/v1" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | ) 28 | 29 | const ( 30 | // TrustFlushCommand - 31 | TrustFlushCommand = "keystone-manage trust_flush" 32 | ) 33 | 34 | // CronJob func 35 | func CronJob( 36 | instance *keystonev1.KeystoneAPI, 37 | labels map[string]string, 38 | annotations map[string]string, 39 | memcached *memcachedv1.Memcached, 40 | ) *batchv1.CronJob { 41 | 42 | args := []string{"-c", TrustFlushCommand + instance.Spec.TrustFlushArgs} 43 | 44 | envVars := map[string]env.Setter{} 45 | envVars["KOLLA_CONFIG_STRATEGY"] = env.SetValue("COPY_ALWAYS") 46 | 47 | parallelism := int32(1) 48 | completions := int32(1) 49 | 50 | // create Volume and VolumeMounts 51 | keystoneCronJobExtraMounts := []keystonev1.KeystoneExtraMounts{} 52 | volumes := getVolumes(instance, keystoneCronJobExtraMounts, KeystoneCronJobPropagation) 53 | volumeMounts := getCronJobVolumeMounts() 54 | 55 | // add CA cert if defined 56 | if instance.Spec.TLS.CaBundleSecretName != "" { 57 | volumes = append(volumes, instance.Spec.TLS.CreateVolume()) 58 | volumeMounts = append(getCronJobVolumeMounts(), instance.Spec.TLS.CreateVolumeMounts(nil)...) 59 | } 60 | 61 | // add MTLS cert if defined 62 | if memcached.GetMemcachedMTLSSecret() != "" { 63 | mtlsVolume := memcached.CreateMTLSVolume() 64 | // Set file permissions to 0440 65 | mtlsVolume.Secret.DefaultMode = func() *int32 { mode := int32(0440); return &mode }() 66 | volumes = append(volumes, mtlsVolume) 67 | volumeMounts = append(volumeMounts, corev1.VolumeMount{ 68 | Name: *memcached.Spec.TLS.MTLS.AuthCertSecret.SecretName, 69 | MountPath: "/etc/pki/tls/certs/mtls.crt", 70 | SubPath: tls.CertKey, 71 | ReadOnly: true, 72 | }, corev1.VolumeMount{ 73 | Name: *memcached.Spec.TLS.MTLS.AuthCertSecret.SecretName, 74 | MountPath: "/etc/pki/tls/private/mtls.key", 75 | SubPath: tls.PrivateKey, 76 | ReadOnly: true, 77 | }, corev1.VolumeMount{ 78 | Name: *memcached.Spec.TLS.MTLS.AuthCertSecret.SecretName, 79 | MountPath: "/etc/pki/tls/certs/mtls-ca.crt", 80 | SubPath: tls.CAKey, 81 | ReadOnly: true, 82 | }) 83 | } 84 | 85 | cronjob := &batchv1.CronJob{ 86 | ObjectMeta: metav1.ObjectMeta{ 87 | Name: ServiceName + "-cron", 88 | Namespace: instance.Namespace, 89 | }, 90 | Spec: batchv1.CronJobSpec{ 91 | Schedule: instance.Spec.TrustFlushSchedule, 92 | Suspend: &instance.Spec.TrustFlushSuspend, 93 | ConcurrencyPolicy: batchv1.ForbidConcurrent, 94 | JobTemplate: batchv1.JobTemplateSpec{ 95 | ObjectMeta: metav1.ObjectMeta{ 96 | Annotations: annotations, 97 | Labels: labels, 98 | }, 99 | Spec: batchv1.JobSpec{ 100 | Parallelism: ¶llelism, 101 | Completions: &completions, 102 | Template: corev1.PodTemplateSpec{ 103 | Spec: corev1.PodSpec{ 104 | Containers: []corev1.Container{ 105 | { 106 | Name: ServiceName + "-cron", 107 | Image: instance.Spec.ContainerImage, 108 | Command: []string{ 109 | "/bin/bash", 110 | }, 111 | Args: args, 112 | Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), 113 | VolumeMounts: volumeMounts, 114 | SecurityContext: baseSecurityContext(), 115 | }, 116 | }, 117 | Volumes: volumes, 118 | RestartPolicy: corev1.RestartPolicyNever, 119 | ServiceAccountName: instance.RbacResourceName(), 120 | SecurityContext: &corev1.PodSecurityContext{ 121 | FSGroup: func() *int64 { gid := int64(42425); return &gid }(), // keystone group 122 | }, 123 | }, 124 | }, 125 | }, 126 | }, 127 | }, 128 | } 129 | if instance.Spec.NodeSelector != nil { 130 | cronjob.Spec.JobTemplate.Spec.Template.Spec.NodeSelector = *instance.Spec.NodeSelector 131 | } 132 | return cronjob 133 | } 134 | -------------------------------------------------------------------------------- /test/kuttl/tests/keystone_tls/01-assert.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # - 1 KeystoneAPI CR 5 | # - Deployment with tls volumes 6 | 7 | apiVersion: keystone.openstack.org/v1beta1 8 | kind: KeystoneAPI 9 | metadata: 10 | name: keystone 11 | spec: 12 | tls: 13 | api: 14 | internal: 15 | secretName: cert-keystone-internal-svc 16 | public: 17 | secretName: cert-keystone-public-svc 18 | caBundleSecretName: combined-ca-bundle 19 | status: 20 | databaseHostname: openstack.keystone-kuttl-tests.svc 21 | readyCount: 1 22 | --- 23 | apiVersion: apps/v1 24 | kind: Deployment 25 | metadata: 26 | name: keystone 27 | spec: 28 | replicas: 1 29 | template: 30 | metadata: 31 | labels: 32 | service: keystone 33 | spec: 34 | containers: 35 | - args: 36 | - -c 37 | - /usr/local/bin/kolla_start 38 | volumeMounts: 39 | - mountPath: /usr/local/bin/container-scripts 40 | name: scripts 41 | readOnly: true 42 | - mountPath: /var/lib/config-data/default 43 | name: config-data 44 | - mountPath: /var/lib/kolla/config_files/config.json 45 | name: config-data 46 | readOnly: true 47 | subPath: keystone-api-config.json 48 | - mountPath: /etc/keystone/fernet-keys 49 | name: fernet-keys 50 | readOnly: true 51 | - mountPath: /etc/keystone/credential-keys 52 | name: credential-keys 53 | readOnly: true 54 | - mountPath: /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem 55 | name: combined-ca-bundle 56 | readOnly: true 57 | subPath: tls-ca-bundle.pem 58 | - mountPath: /var/lib/config-data/tls/certs/internal.crt 59 | name: internal-tls-certs 60 | readOnly: true 61 | subPath: tls.crt 62 | - mountPath: /var/lib/config-data/tls/private/internal.key 63 | name: internal-tls-certs 64 | readOnly: true 65 | subPath: tls.key 66 | - mountPath: /var/lib/config-data/tls/certs/public.crt 67 | name: public-tls-certs 68 | readOnly: true 69 | subPath: tls.crt 70 | - mountPath: /var/lib/config-data/tls/private/public.key 71 | name: public-tls-certs 72 | readOnly: true 73 | subPath: tls.key 74 | volumes: 75 | - name: scripts 76 | secret: 77 | defaultMode: 493 78 | secretName: keystone-scripts 79 | - name: config-data 80 | secret: 81 | defaultMode: 420 82 | secretName: keystone-config-data 83 | - name: fernet-keys 84 | secret: 85 | defaultMode: 420 86 | items: 87 | - key: FernetKeys0 88 | path: "0" 89 | - key: FernetKeys1 90 | path: "1" 91 | - key: FernetKeys2 92 | path: "2" 93 | - key: FernetKeys3 94 | path: "3" 95 | - key: FernetKeys4 96 | path: "4" 97 | secretName: keystone 98 | - name: credential-keys 99 | secret: 100 | defaultMode: 420 101 | items: 102 | - key: CredentialKeys0 103 | path: "0" 104 | - key: CredentialKeys1 105 | path: "1" 106 | secretName: keystone 107 | - name: combined-ca-bundle 108 | secret: 109 | defaultMode: 292 110 | secretName: combined-ca-bundle 111 | - name: internal-tls-certs 112 | secret: 113 | defaultMode: 256 114 | secretName: cert-keystone-internal-svc 115 | - name: public-tls-certs 116 | secret: 117 | defaultMode: 256 118 | secretName: cert-keystone-public-svc 119 | --- 120 | # the actual addresses of the apiEndpoints are platform specific, so we can't rely on 121 | # kuttl asserts to check them. This short script gathers the addresses and checks that 122 | # the three endpoints are defined and their addresses follow the default pattern 123 | apiVersion: kuttl.dev/v1beta1 124 | kind: TestAssert 125 | commands: 126 | - script: | 127 | # the actual addresses of the apiEndpoints are platform specific, so we can't rely on 128 | # kuttl asserts to check them. This short script gathers the addresses and checks that 129 | # the three endpoints are defined and their addresses follow the default pattern 130 | template='{{.status.apiEndpoints.internal}}{{":"}}{{.status.apiEndpoints.public}}{{"\n"}}' 131 | regex="https:\/\/keystone-internal.$NAMESPACE.*:https:\/\/keystone-public.$NAMESPACE.*" 132 | apiEndpoints=$(oc get -n $NAMESPACE KeystoneAPI keystone -o go-template="$template") 133 | matches=$(echo "$apiEndpoints" | sed -e "s?$regex??") 134 | if [ -z "$matches" ]; then 135 | exit 0 136 | else 137 | echo "Endpoints URLS: $apiEndpoints do not match regex" 138 | exit 1 139 | fi 140 | -------------------------------------------------------------------------------- /api/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/openstack-k8s-operators/keystone-operator/api 2 | 3 | go 1.24.4 4 | 5 | require ( 6 | github.com/go-logr/logr v1.4.3 7 | github.com/google/uuid v1.6.0 8 | github.com/gophercloud/gophercloud/v2 v2.8.0 9 | github.com/onsi/gomega v1.38.2 10 | github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251205192058-5cfbada0ab96 11 | github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251122131503-b76943960b6c 12 | github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20251122131503-b76943960b6c 13 | github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20251122131503-b76943960b6c 14 | github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20251122131503-b76943960b6c 15 | golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 16 | k8s.io/api v0.31.14 17 | k8s.io/apimachinery v0.31.14 18 | k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d 19 | sigs.k8s.io/controller-runtime v0.19.7 20 | ) 21 | 22 | require ( 23 | github.com/beorn7/perks v1.0.1 // indirect 24 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 25 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 26 | github.com/emicklei/go-restful/v3 v3.12.2 // indirect 27 | github.com/evanphx/json-patch v5.9.11+incompatible // indirect 28 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 29 | github.com/fsnotify/fsnotify v1.9.0 // indirect 30 | github.com/fxamacker/cbor/v2 v2.9.0 // indirect 31 | github.com/go-openapi/jsonpointer v0.21.1 // indirect 32 | github.com/go-openapi/jsonreference v0.21.0 // indirect 33 | github.com/go-openapi/swag v0.23.1 // indirect 34 | github.com/gogo/protobuf v1.3.2 // indirect 35 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 36 | github.com/golang/protobuf v1.5.4 // indirect 37 | github.com/google/gnostic-models v0.7.0 // indirect 38 | github.com/google/go-cmp v0.7.0 // indirect 39 | github.com/google/gofuzz v1.2.0 // indirect 40 | github.com/imdario/mergo v0.3.16 // indirect 41 | github.com/josharian/intern v1.0.0 // indirect 42 | github.com/json-iterator/go v1.1.12 // indirect 43 | github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 // indirect 44 | github.com/mailru/easyjson v0.9.0 // indirect 45 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 46 | github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect 47 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 48 | github.com/openshift/api v3.9.0+incompatible // indirect 49 | github.com/pkg/errors v0.9.1 // indirect 50 | github.com/prometheus/client_golang v1.22.0 // indirect 51 | github.com/prometheus/client_model v0.6.2 // indirect 52 | github.com/prometheus/common v0.65.0 // indirect 53 | github.com/prometheus/procfs v0.16.1 // indirect 54 | github.com/spf13/pflag v1.0.7 // indirect 55 | github.com/x448/float16 v0.8.4 // indirect 56 | go.yaml.in/yaml/v2 v2.4.2 // indirect 57 | go.yaml.in/yaml/v3 v3.0.4 // indirect 58 | golang.org/x/net v0.43.0 // indirect 59 | golang.org/x/oauth2 v0.30.0 // indirect 60 | golang.org/x/sys v0.36.0 // indirect 61 | golang.org/x/term v0.35.0 // indirect 62 | golang.org/x/text v0.29.0 // indirect 63 | golang.org/x/time v0.12.0 // indirect 64 | gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect 65 | google.golang.org/protobuf v1.36.7 // indirect 66 | gopkg.in/inf.v0 v0.9.1 // indirect 67 | gopkg.in/yaml.v3 v3.0.1 // indirect 68 | k8s.io/apiextensions-apiserver v0.33.2 // indirect 69 | k8s.io/client-go v0.31.14 // indirect 70 | k8s.io/klog/v2 v2.130.1 // indirect 71 | k8s.io/kube-openapi v0.0.0-20250902184714-7fc278399c7f // indirect 72 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 73 | sigs.k8s.io/randfill v1.0.0 // indirect 74 | sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect 75 | sigs.k8s.io/yaml v1.6.0 // indirect 76 | ) 77 | 78 | // mschuppert: map to latest commit from release-4.18 tag 79 | // must consistent within modules and service operators 80 | replace github.com/openshift/api => github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e //allow-merging 81 | 82 | // pin these to avoid later versions pulled by rabbitmq 83 | replace k8s.io/apimachinery => k8s.io/apimachinery v0.31.14 //allow-merging 84 | 85 | replace k8s.io/api => k8s.io/api v0.31.14 //allow-merging 86 | 87 | replace k8s.io/apiserver => k8s.io/apiserver v0.31.14 //allow-merging 88 | 89 | replace k8s.io/client-go => k8s.io/client-go v0.31.14 //allow-merging 90 | 91 | replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.31.14 //allow-merging 92 | 93 | replace k8s.io/cli-runtime => k8s.io/cli-runtime v0.31.14 //allow-merging 94 | 95 | replace k8s.io/code-generator => k8s.io/code-generator v0.31.14 //allow-merging 96 | 97 | replace k8s.io/component-base => k8s.io/component-base v0.31.14 //allow-merging 98 | 99 | replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging 100 | -------------------------------------------------------------------------------- /internal/keystone/volumes.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package keystone 17 | 18 | import ( 19 | "fmt" 20 | keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" 21 | "github.com/openstack-k8s-operators/lib-common/modules/storage" 22 | corev1 "k8s.io/api/core/v1" 23 | ) 24 | 25 | // getVolumes - service volumes 26 | func getVolumes( 27 | instance *keystonev1.KeystoneAPI, 28 | extraVol []keystonev1.KeystoneExtraMounts, 29 | svc []storage.PropagationType, 30 | ) []corev1.Volume { 31 | name := instance.Name 32 | var scriptsVolumeDefaultMode int32 = 0755 33 | var config0640AccessMode int32 = 0644 34 | 35 | fernetKeys := []corev1.KeyToPath{} 36 | numberKeys := int(*instance.Spec.FernetMaxActiveKeys) 37 | 38 | for i := range numberKeys { 39 | fernetKeys = append( 40 | fernetKeys, 41 | corev1.KeyToPath{ 42 | Key: fmt.Sprintf("FernetKeys%d", i), 43 | Path: fmt.Sprintf("%d", i), 44 | }, 45 | ) 46 | } 47 | 48 | res := []corev1.Volume{ 49 | { 50 | Name: "scripts", 51 | VolumeSource: corev1.VolumeSource{ 52 | Secret: &corev1.SecretVolumeSource{ 53 | DefaultMode: &scriptsVolumeDefaultMode, 54 | SecretName: name + "-scripts", 55 | }, 56 | }, 57 | }, 58 | { 59 | Name: "config-data", 60 | VolumeSource: corev1.VolumeSource{ 61 | Secret: &corev1.SecretVolumeSource{ 62 | DefaultMode: &config0640AccessMode, 63 | SecretName: name + "-config-data", 64 | }, 65 | }, 66 | }, 67 | { 68 | Name: "fernet-keys", 69 | VolumeSource: corev1.VolumeSource{ 70 | Secret: &corev1.SecretVolumeSource{ 71 | SecretName: ServiceName, 72 | Items: fernetKeys, 73 | }, 74 | }, 75 | }, 76 | { 77 | Name: "credential-keys", 78 | VolumeSource: corev1.VolumeSource{ 79 | Secret: &corev1.SecretVolumeSource{ 80 | SecretName: ServiceName, 81 | Items: []corev1.KeyToPath{ 82 | { 83 | Key: "CredentialKeys0", 84 | Path: "0", 85 | }, 86 | { 87 | Key: "CredentialKeys1", 88 | Path: "1", 89 | }, 90 | }, 91 | }, 92 | }, 93 | }, 94 | } 95 | for _, exv := range extraVol { 96 | for _, vol := range exv.Propagate(svc) { 97 | for _, v := range vol.Volumes { 98 | volumeSource, _ := v.ToCoreVolumeSource() 99 | convertedVolume := corev1.Volume{ 100 | Name: v.Name, 101 | VolumeSource: *volumeSource, 102 | } 103 | res = append(res, convertedVolume) 104 | } 105 | } 106 | } 107 | return res 108 | } 109 | 110 | // getVolumeMounts - general VolumeMounts 111 | func getVolumeMounts( 112 | extraVol []keystonev1.KeystoneExtraMounts, 113 | svc []storage.PropagationType, 114 | ) []corev1.VolumeMount { 115 | vm := []corev1.VolumeMount{ 116 | { 117 | Name: "scripts", 118 | MountPath: "/usr/local/bin/container-scripts", 119 | ReadOnly: true, 120 | }, 121 | { 122 | Name: "config-data", 123 | MountPath: "/var/lib/config-data/default", 124 | ReadOnly: false, 125 | }, 126 | { 127 | Name: "config-data", 128 | MountPath: "/var/lib/kolla/config_files/config.json", 129 | SubPath: "keystone-api-config.json", 130 | ReadOnly: true, 131 | }, 132 | { 133 | MountPath: "/etc/keystone/fernet-keys", 134 | ReadOnly: true, 135 | Name: "fernet-keys", 136 | }, 137 | { 138 | MountPath: "/etc/keystone/credential-keys", 139 | ReadOnly: true, 140 | Name: "credential-keys", 141 | }, 142 | } 143 | for _, exv := range extraVol { 144 | for _, vol := range exv.Propagate(svc) { 145 | vm = append(vm, vol.Mounts...) 146 | } 147 | } 148 | return vm 149 | } 150 | 151 | // getCronJobVolumeMounts - cronjob volumeMounts 152 | func getCronJobVolumeMounts() []corev1.VolumeMount { 153 | return []corev1.VolumeMount{ 154 | { 155 | Name: "config-data", 156 | MountPath: "/etc/keystone/keystone.conf", 157 | SubPath: "keystone.conf", 158 | ReadOnly: true, 159 | }, 160 | { 161 | Name: "config-data", 162 | MountPath: "/etc/my.cnf", 163 | SubPath: "my.cnf", 164 | ReadOnly: true, 165 | }, 166 | { 167 | Name: "fernet-keys", 168 | MountPath: "/etc/keystone/fernet-keys", 169 | ReadOnly: true, 170 | }, 171 | } 172 | } 173 | 174 | // getDBSyncVolumeMounts - cronjob volumeMounts 175 | func getDBSyncVolumeMounts() []corev1.VolumeMount { 176 | return []corev1.VolumeMount{ 177 | { 178 | Name: "config-data", 179 | MountPath: "/etc/keystone/keystone.conf", 180 | SubPath: "keystone.conf", 181 | ReadOnly: true, 182 | }, 183 | { 184 | Name: "config-data", 185 | MountPath: "/etc/my.cnf", 186 | SubPath: "my.cnf", 187 | ReadOnly: true, 188 | }, 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /test/kuttl/common/tls_cert_keystone-public-svc.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Cert created with: 3 | # 4 | # apiVersion: cert-manager.io/v1 5 | # kind: Certificate 6 | # metadata: 7 | # name: kuttl-keystone-public-svc 8 | # namespace: openstack 9 | # spec: 10 | # dnsNames: 11 | # - keystone-public.openstack.svc 12 | # duration: 87600h0m0s 13 | # issuerRef: 14 | # group: cert-manager.io 15 | # kind: Issuer 16 | # name: rootca-kuttl-public 17 | # secretName: cert-keystone-public-svc 18 | # secretTemplate: {} 19 | # usages: 20 | # - key encipherment 21 | # - digital signature 22 | # - server auth 23 | 24 | apiVersion: v1 25 | data: 26 | ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJlekNDQVNLZ0F3SUJBZ0lRTkhER1lzQnM3OThpYkREN3EvbzJsakFLQmdncWhrak9QUVFEQWpBZU1Sd3cKR2dZRFZRUURFeE55YjI5MFkyRXRhM1YwZEd3dGNIVmliR2xqTUI0WERUSTBNREV4TlRFd01UVXpObG9YRFRNMApNREV4TWpFd01UVXpObG93SGpFY01Cb0dBMVVFQXhNVGNtOXZkR05oTFd0MWRIUnNMWEIxWW14cFl6QlpNQk1HCkJ5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCRDc4YXZYcWhyaEM1dzhzOVdrZDRJcGJlRXUwM0NSK1hYVWQKa0R6T1J5eGE5d2NjSWREaXZiR0pqSkZaVFRjVm1ianExQk1Zc2pyMTJVSUU1RVQzVmxxalFqQkFNQTRHQTFVZApEd0VCL3dRRUF3SUNwREFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQjBHQTFVZERnUVdCQlRLSml6V1VKOWVVS2kxCmRzMGxyNmM2c0Q3RUJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRUFpQklad1lxNjFCcU1KYUI2VWNGb1JzeGVjd0gKNXovek1PZHJPeWUwbU5pOEpnSWdRTEI0d0RLcnBmOXRYMmxvTSswdVRvcEFEU1lJbnJjZlZ1NEZCdVlVM0lnPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 27 | tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNiVENDQWhPZ0F3SUJBZ0lSQUtacXlMbUhLNC9VRTZmMi9LNWxiQnN3Q2dZSUtvWkl6ajBFQXdJd0hqRWMKTUJvR0ExVUVBeE1UY205dmRHTmhMV3QxZEhSc0xYQjFZbXhwWXpBZUZ3MHlOREF4TVRVeE1ESXdOVFJhRncwegpOREF4TVRJeE1ESXdOVFJhTUFBd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUMxCjhDcFJRVG1abHNzSUlmZ2hIK2ltUUtFMFdZVlJOeS8vMVM0aDVtV2tBcUZiVkhoUmptbFJ2cCtQUWpKOU16TDUKMXpXdmYxandEQ2pzYUxvL2FwSW9OSXJIcjN4TTRoYWl0emU0RjFwZzNoL3MvblExNWN5Q2U5dHdHR0RuWEllMwo2djBuNE9LNnAwSWJjcVk2Q1RBMTBwcGJZa3V6bzdVRkx6ZWxsc1ZhRlhzZ21JWDg4bTRXNmNBTi84cjJPWUI3Ck9HM0ZNOXAxSUFxT0hyT21EelFlTldqOUVjQy9TSCs5MGg4c1FyY1pvMWtWa1g1b2tpSUhDZjRlc2o3Q08rTGgKR3lsTmZyRzl6QTlPM0c3QVNDWVdPVWwyZTBhNHhZbE9QMmI4ejFEV3NIMTBVYXVsZHlRQXNtbkhtaW1VNzBmKwpEazZkQ1hXVHN4cGZ2cXphOVR4YkFnTUJBQUdqZ1lRd2dZRXdEZ1lEVlIwUEFRSC9CQVFEQWdXZ01CTUdBMVVkCkpRUU1NQW9HQ0NzR0FRVUZCd01CTUF3R0ExVWRFd0VCL3dRQ01BQXdId1lEVlIwakJCZ3dGb0FVeWlZczFsQ2YKWGxDb3RYYk5KYStuT3JBK3hBUXdLd1lEVlIwUkFRSC9CQ0V3SDRJZGEyVjVjM1J2Ym1VdGNIVmliR2xqTG05dwpaVzV6ZEdGamF5NXpkbU13Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUlnTzAzT2JmNm9uV2RiZG4xa282OVpuTFhMCmtQSHFYU3VRNlcxTDFvY3NDR3NDSVFEakEyVm9pWVdYN0hzSjVGNkZYV3FsZnl0RmduVVgvTmhvT1lIVnB2TWQKSGc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== 28 | tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdGZBcVVVRTVtWmJMQ0NINElSL29wa0NoTkZtRlVUY3YvOVV1SWVabHBBS2hXMVI0ClVZNXBVYjZmajBJeWZUTXkrZGMxcjM5WThBd283R2k2UDJxU0tEU0t4Njk4VE9JV29yYzN1QmRhWU40ZjdQNTAKTmVYTWdudmJjQmhnNTF5SHQrcjlKK0RpdXFkQ0czS21PZ2t3TmRLYVcySkxzNk8xQlM4M3BaYkZXaFY3SUppRgovUEp1RnVuQURmL0s5am1BZXpodHhUUGFkU0FLamg2enBnODBIalZvL1JIQXYwaC92ZElmTEVLM0dhTlpGWkYrCmFKSWlCd24rSHJJK3dqdmk0UnNwVFg2eHZjd1BUdHh1d0VnbUZqbEpkbnRHdU1XSlRqOW0vTTlRMXJCOWRGR3IKcFhja0FMSnB4NW9wbE85SC9nNU9uUWwxazdNYVg3NnMydlU4V3dJREFRQUJBb0lCQUd0eVdvdUNLYkk3Qzh6Ugp3dWhOSCtpUFlxUzMrYlB0RTd2UytsdXE1WHZtMGNST0xvQjd5bGNzYks3K09UTVhlWk56TlpGZmMvYlFONXJtCmZwZlZLRnYySzcraU01WjBMMG9KU2k2K0cvSDVQSUdLQkxlUDd5ZGdYa2ZsSGRXRkgrSE9OWlBIakI4UGlFc04KZW4zcnp6ejZFNDdFamxDWTdkOFI4NXNuWDRYREN2bG1CQnhvcnpqVERuK1dTWWpKS09SSk5zY3oxQXFYR1VjVwpQaHRNYkwybC8zN2hPbTA4SjRRWXowTWduOWE5VUFXLzFNS2lXbHVpc1NHNG9YaFNPS1hkdk1IS3VxS09sUDJzCk9xWjBlR3JBNmpKdWlmZVY2Q2NIU2p3VUgwdHpiMmdZQVM2cm5RQlREbFkxR1I4Skx0YWhWREtqdUwyV0hjclkKbHhCOGZBRUNnWUVBMWpxc01weFo5cG9LNkpmRDZzVTU0ZUU1UWlNYlB0MERRZjlYU1J6NW5zQXlraHdKWEZDVwpKWTNiU3BhcGREeEgzakpDQ0VzN3NSWUhUeDRzNVJQSldtWC9oZTFwVEs4TDVlaFV2TmVudG5nVTB3aE4rZVEzCjl4Sk1VbHVYdGkvU0FpNi9jQk5HY1ZjQjFGRStmVzY0VDhqYVVQakRrL0Z2dFRXOXkzZnNVZnNDZ1lFQTJXbXMKYStuZ2RaS24rVTlCMlFCTXB0K0RLL0txNVF6RW1qZDVMSjJMb3FLUjhGbjlpVVZoUVljUEpobDJVV3VjTTl0RQp0QUlYdEY0anVUejlqUUNMMGQ5a09DeCswdTBKUFZJejdlVmFFVGs1enF0azRsTnhVUkJhQ3pCUEJkdjZJd3BDCkR4UXJWRXBXYlMra1JvYTNKSElOdHdjUWt2Nk50N1JIajd1WEVTRUNnWUJsREozbTdZc2Q0QUZmUHg4Qm9YQXgKRkt5T2ZzSytQei9uSkl0R2lHMVNMWFJ0S041ZGRnR3N5eUh5SitqY1ZBYk9UMFNJWnZ4TUJva0NEOGk3Y1Q3Ygo3aHErVUlNSDBkVzU1NEg0NVh4TmZJek9FaSs5dktHTllFc3gyZFJROG5PTDVnTVUyWEt6eVllcVgzd3JiRXR5CkR0cXpzUE9IMkMySiswU0FNaHY5ZXdLQmdRQ2szeWs5TUwvaUNWUk9rTmNybTdtRk5xeS9rQ2dleU43eTRDeUoKTS9RbllrZHYwSjZmRWJrZU96QzJ3TXBrRmtuL1hVR3RqSVN6YUV5STlnS0ZnaXVGL1hWL3orWmhTQllncFl6eAoxR0xIK3ZDbWxIMU4wTjkzRFFKcng3ZTFoc3NhOVhXQS85ZVg5VU96UzFTMWt3V2hvc2haeXdhN29rU1FVaXVPCmlVQ1hZUUtCZ1FEUVZUVHc3WUY3QzNTVmg5OWRObUdTaHV2LzZ2aTJmNDlOMklGMURNQ1haaEpoOUVZck9TV2kKY05oakxGRFhmdzVlZlFURWU3Ykx5bTJGVDd0YnZFSm5USHFyakVuUDRUWExqZnczL3RiQ3RxWVNZRlRqdThFUApadHVwd21ZWjhFVU1pSnVHS2l2SExmSjk2dy8xR21BOHVCZUVtV05YRW9FUU1ySmxuM3g5d3c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= 29 | kind: Secret 30 | metadata: 31 | annotations: 32 | cert-manager.io/alt-names: keystone-public.openstack.svc 33 | cert-manager.io/certificate-name: keystone-public-svc 34 | cert-manager.io/common-name: "" 35 | cert-manager.io/ip-sans: "" 36 | cert-manager.io/issuer-group: cert-manager.io 37 | cert-manager.io/issuer-kind: Issuer 38 | cert-manager.io/issuer-name: rootca-public 39 | cert-manager.io/uri-sans: "" 40 | labels: 41 | controller.cert-manager.io/fao: "true" 42 | name: cert-keystone-public-svc 43 | type: kubernetes.io/tls 44 | -------------------------------------------------------------------------------- /test/kuttl/common/tls_cert_keystone-internal-svc.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Cert created with: 3 | # 4 | # apiVersion: cert-manager.io/v1 5 | # kind: Certificate 6 | # metadata: 7 | # name: keystone-kuttl-internal-svc 8 | # namespace: openstack 9 | # spec: 10 | # dnsNames: 11 | # - keystone-internal.openstack.svc 12 | # duration: 87600h0m0s 13 | # issuerRef: 14 | # group: cert-manager.io 15 | # kind: Issuer 16 | # name: rootca-kuttl-internal 17 | # secretName: cert-keystone-internal-svc 18 | # secretTemplate: {} 19 | # usages: 20 | # - key encipherment 21 | # - digital signature 22 | # - server auth 23 | # 24 | 25 | apiVersion: v1 26 | data: 27 | ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJmekNDQVNhZ0F3SUJBZ0lRUWxlcTNZcDBtU2kwVDNiTm03Q29UVEFLQmdncWhrak9QUVFEQWpBZ01SNHcKSEFZRFZRUURFeFZ5YjI5MFkyRXRhM1YwZEd3dGFXNTBaWEp1WVd3d0hoY05NalF3TVRFMU1URTBOelUwV2hjTgpNelF3TVRFeU1URTBOelUwV2pBZ01SNHdIQVlEVlFRREV4VnliMjkwWTJFdGEzVjBkR3d0YVc1MFpYSnVZV3d3CldUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTRk9rNHJPUldVUGhoTjUrK09EN1I2MW5Gb1lBY0QKenpvUS91SW93NktjeGhwRWNQTDFxb3ZZUGxUYUJabEh3c2FpNE50VHA4aDA1RHVRSGZKOE9JNXFvMEl3UURBTwpCZ05WSFE4QkFmOEVCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVXE3TGtFSk1TCm1MOVpKWjBSOUluKzZkclhycEl3Q2dZSUtvWkl6ajBFQXdJRFJ3QXdSQUlnVlN1K00ydnZ3QlF3eTJHMVlhdkkKQld2RGtSNlRla0I5U0VqdzJIblRSMWtDSUZSNFNkWGFPQkFGWjVHa2RLWCtSY2IzaDFIZm52eFJEVW96bTl2agphenp3Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K 28 | tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNjRENDQWhhZ0F3SUJBZ0lRZG5oL3ZsTVFBZ1lEd3BpYkRDbXVhekFLQmdncWhrak9QUVFEQWpBZ01SNHcKSEFZRFZRUURFeFZ5YjI5MFkyRXRhM1YwZEd3dGFXNTBaWEp1WVd3d0hoY05NalF3TVRFMk1UQTFNekUxV2hjTgpNelF3TVRFek1UQTFNekUxV2pBQU1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBCndidzhpWGZEd1RCYlRCTStlTVFPSktCSzVIeGV0bVB2NG5yWkVFc3V2QjRzbTZDNW1ZdUlmRGM2Y2xaV1FjUDEKbG1EMWNkNlYvTS9ydG4vNXFremZiSlJ0SlFiNFgwQzdJWFMxTDZJN3pWMDlORnhpZnZrRDR0Vy9MbEVTTHh2OAp0amZuYzZlbzhlZjZMYkxjby84Wjhkd245NVZSYmhGMWFYWXVyYlBpWVBoUDZTa29Ta20wVWpZK1NLUWhJMVoxCkMrUVBLWlZEQ1EvQkl3UmIrdUVZWCtUdWdmOVNTbkQrbEpLNFlMcVJpSW5LbjNVWTRzSFpTR3lmR0RBck5mSlQKOHUxcVI3TXAwWWRUL2tJb3pnbXp6aC9Jd3ZRRG16V05sZ0M4TmtRcmdmOXBrR2lyZFlLRUpQRmlFZjFTZXp3YwpMOG1Xc0hRYVA2bE95ZUVicW9nUUtRSURBUUFCbzRHR01JR0RNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WCkhTVUVEREFLQmdnckJnRUZCUWNEQVRBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkt1eTVCQ1QKRXBpL1dTV2RFZlNKL3VuYTE2NlNNQzBHQTFVZEVRRUIvd1FqTUNHQ0gydGxlWE4wYjI1bExXbHVkR1Z5Ym1GcwpMbTl3Wlc1emRHRmpheTV6ZG1Nd0NnWUlLb1pJemowRUF3SURTQUF3UlFJZ01Wa0w0dk8vbnJORXl2Z2pYdVFPCmxncU5iRVdQOEw4R3RYbFlPaWcxT2hvQ0lRRGUxTzgvaHdxYlpySWFzSkZzZzlMM2FzcysrWVc5bXdBUzVmWncKOStqRVZ3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= 29 | tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBd2J3OGlYZkR3VEJiVEJNK2VNUU9KS0JLNUh4ZXRtUHY0bnJaRUVzdXZCNHNtNkM1Cm1ZdUlmRGM2Y2xaV1FjUDFsbUQxY2Q2Vi9NL3J0bi81cWt6ZmJKUnRKUWI0WDBDN0lYUzFMNkk3elYwOU5GeGkKZnZrRDR0Vy9MbEVTTHh2OHRqZm5jNmVvOGVmNkxiTGNvLzhaOGR3bjk1VlJiaEYxYVhZdXJiUGlZUGhQNlNrbwpTa20wVWpZK1NLUWhJMVoxQytRUEtaVkRDUS9CSXdSYit1RVlYK1R1Z2Y5U1NuRCtsSks0WUxxUmlJbktuM1VZCjRzSFpTR3lmR0RBck5mSlQ4dTFxUjdNcDBZZFQva0lvemdtenpoL0l3dlFEbXpXTmxnQzhOa1FyZ2Y5cGtHaXIKZFlLRUpQRmlFZjFTZXp3Y0w4bVdzSFFhUDZsT3llRWJxb2dRS1FJREFRQUJBb0lCQVFDQ0hweUdNK05OY040UQo1V2Z6RXJMeEZKdlloRlBVcXFDbWU1NG9uR1ppUU4zekZPc3pYbzBuNkt3ZnVTOHI4cUtUQXNJM1hhbGRhSVRIClNZTDFSN1pVSmdoOGN3Y0VhdVNFbnU5R2MrODRpbVFlTStLUHAwNWQzdlFOOXJPQTRvcEVGSjRtaHJnbzZZYVYKaE9rK1dJc2piNXVFWlV5UTRiYjdRejRzdW9IVVlDYXFkVGlqU1lYQzNOd092YUlwa3pTNEo0cU5CUlhyYnNWSwowaGt4ZFNIY1hKNEREN0hybktpcEsxT2xUbUVObVZYbmlaNnRPcWc2eUNFeXFteWN0UnlUVTZRRzVPbVM2clJVCm82Z25EclA1TlgwRUhuakY3b1lka0JVbGJxWk96UHVGbG5CdUVKOFpTUEtOZHA5ejhuS3lqbGJiL0YxWGRDdEkKZERhVUhmREZBb0dCQVBFZkZZbDhPb2VhU21oSElKcGpxd3RCanYrRjFuOXNJbHNuZWNyQ1JmN243RW53d1hXaQpReStXQ3l6aDJGRVVad1dod2RQeXFJT3NVaG1vaXBIQmN3NlVUaW9xalM4SlpvSDlURFBQUEd5OXIwMHZwRkNuCnFkdjNXMkhWVytRckMrWk1nc2ZKdUlTTnFtbFdFeHpCNFBJQWRHQTdKVzFMY0ZCcG1Zd25DdXZyQW9HQkFNMncKbS94cVRhMmgySjFnNUI0elE5UnBhM280SEoyL2pTaHEzNW9heVNGNWJDYWtnWGRxek0yU0FwQ0x4dzlvY3doRAp3WWRaMWliaHl6b1dDQVZZZ0RlaXViUi96ZTN3Nzk4NktScUNmNnptMk5HOEoxODVDZDdKSjBiaTZBTTgvalpTCnFqWkJIK0FqanF2aFFJM0FMMEdzNlFvc2Z3L3hOL2k1cG00UWM5TTdBb0dBZjZCbFpQVmxnWnN3WVV1c3ZTdWUKUUlIOTc5Qm12ZUY5dWVRR09rVmtpVTAzSzlnTWZuaFp1WmxnNXV2UDlQS29xVGw2Zi9aRUxoWUxDdHZFSk94UgpPMWxTbWswVmw5MFE3aU1scjVLMHVCWWE4TzhUdVVGVnprRjZsQ2s3ejJUZGtwUFM4VzhiaE1YN2VtLytBODIzCmhFQ3JXTGhWMGlrSkZQY2dPQ2YrUnVzQ2dZQTYvcld1cnhxNmUxb3l3WENNVE8zZWhhSUMrd2NTSTdlcjZRTmIKSXVXZlNVRkEwQndtRVNiT3ExczY5Q3hTK2dWTVVJcTRkSWJjdmhSWkE2cW5SZHY0bVI2a2E2ZTM0RXdjZllUKwppb0Z1S1FQMUcvODY2NVF1SndteDVqRGZoT1h3MU1MbkxzU2l0L0FhMGs5K21LbTFMNC9qa0NHZGcvVW16TEMwCmp0bDVzd0tCZ0VPTVI3ODVLT2hyNXFoWmE2b0MvU25JeEptS1FxTWdXU0NGV1pGMDZrVlRnSmthb1hwUEl0bUIKOUZGbE1nTTJSeC91S2V3YTNDSTdQK240ek1uYSswTmhDL0RwNkMxVFVsVWlrcnJYQ3I5a1NPR2dXaEFISDljTwozRENvdkhOcE1PaG51dnhoMlpDeTdYbjFJeGgxWXdlYnVobFZzeTFvR0tDQ0lJb00rOVg1Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== 30 | kind: Secret 31 | metadata: 32 | annotations: 33 | cert-manager.io/alt-names: keystone-internal.openstack.svc 34 | cert-manager.io/certificate-name: keystone-internal-svc 35 | cert-manager.io/common-name: "" 36 | cert-manager.io/ip-sans: "" 37 | cert-manager.io/issuer-group: cert-manager.io 38 | cert-manager.io/issuer-kind: Issuer 39 | cert-manager.io/issuer-name: rootca-internal 40 | cert-manager.io/uri-sans: "" 41 | labels: 42 | controller.cert-manager.io/fao: "true" 43 | name: cert-keystone-internal-svc 44 | type: kubernetes.io/tls 45 | -------------------------------------------------------------------------------- /hack/run_with_local_webhook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | # Define a cleanup function 5 | cleanup() { 6 | echo "Caught signal, cleaning up local webhooks..." 7 | ./hack/clean_local_webhook.sh 8 | exit 0 9 | } 10 | 11 | # Set trap to catch SIGINT and SIGTERM 12 | trap cleanup SIGINT SIGTERM 13 | 14 | TMPDIR=${TMPDIR:-"/tmp/k8s-webhook-server/serving-certs"} 15 | SKIP_CERT=${SKIP_CERT:-false} 16 | CRC_IP=${CRC_IP:-$(/sbin/ip -o -4 addr list crc | awk '{print $4}' | cut -d/ -f1)} 17 | FIREWALL_ZONE=${FIREWALL_ZONE:-"libvirt"} 18 | WEBHOOK_PORT=${WEBHOOK_PORT:-${WEBHOOK_PORT}} 19 | 20 | #Open ${WEBHOOK_PORT} 21 | sudo firewall-cmd --zone=${FIREWALL_ZONE} --add-port=${WEBHOOK_PORT}/tcp 22 | sudo firewall-cmd --runtime-to-permanent 23 | 24 | # Generate the certs and the ca bundle 25 | if [ "$SKIP_CERT" = false ] ; then 26 | mkdir -p ${TMPDIR} 27 | rm -rf ${TMPDIR}/* || true 28 | 29 | openssl req -newkey rsa:2048 -days 3650 -nodes -x509 \ 30 | -subj "/CN=${HOSTNAME}" \ 31 | -addext "subjectAltName = IP:${CRC_IP}" \ 32 | -keyout ${TMPDIR}/tls.key \ 33 | -out ${TMPDIR}/tls.crt 34 | 35 | cat ${TMPDIR}/tls.crt ${TMPDIR}/tls.key | base64 -w 0 > ${TMPDIR}/bundle.pem 36 | 37 | fi 38 | 39 | CA_BUNDLE=`cat ${TMPDIR}/bundle.pem` 40 | 41 | # Patch the webhook(s) 42 | cat >> ${TMPDIR}/patch_webhook_configurations.yaml < "${CSV_FILE}" 114 | 115 | printf \ 116 | "\n\tNow patching operator CSV to remove its OLM deployment and associated webhooks. 117 | The original OLM version of the operator's CSV has been copied to %s. To restore it, use: 118 | oc patch -n openstack-operators %s --type=merge --patch-file=%s\n\n" "${CSV_FILE}" "${CSV_NAME}" "${CSV_FILE}" 119 | fi 120 | 121 | oc patch "${CSV_NAME}" -n openstack-operators --type=json -p="[{'op': 'replace', 'path': '/spec/install/spec/deployments/0/spec/replicas', 'value': 0}]" 122 | oc patch "${CSV_NAME}" -n openstack-operators --type=json -p="[{'op': 'replace', 'path': '/spec/webhookdefinitions', 'value': []}]" 123 | else 124 | # Handle operator deployed by Openstack Initialization resource 125 | CSV_NAME="$(oc get csv -n openstack-operators -l operators.coreos.com/openstack-operator.openstack-operators -o name)" 126 | 127 | printf \ 128 | "\n\tNow patching openstack operator CSV to scale down deployment resource. 129 | To restore it, use: 130 | oc patch "${CSV_NAME}" -n openstack-operators --type=json -p=\"[{'op': 'replace', 'path': '/spec/install/spec/deployments/0/spec/replicas', 'value': 1}]\"" 131 | 132 | oc patch "${CSV_NAME}" -n openstack-operators --type=json -p="[{'op': 'replace', 'path': '/spec/install/spec/deployments/0/spec/replicas', 'value': 0}]" 133 | oc scale --replicas=0 -n openstack-operators deploy/keystone-operator-controller-manager 134 | fi 135 | 136 | go run ./cmd/main.go -metrics-bind-address ":${METRICS_PORT}" -health-probe-bind-address ":${HEALTH_PORT}" -pprof-bind-address ":${PPROF_PORT}" -webhook-bind-address "${WEBHOOK_PORT}" 137 | -------------------------------------------------------------------------------- /internal/keystone/bootstrap.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | // Package keystone contains keystone service bootstrap functionality. 17 | package keystone 18 | 19 | import ( 20 | memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" 21 | keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" 22 | 23 | "github.com/openstack-k8s-operators/lib-common/modules/common/env" 24 | "github.com/openstack-k8s-operators/lib-common/modules/common/tls" 25 | 26 | batchv1 "k8s.io/api/batch/v1" 27 | corev1 "k8s.io/api/core/v1" 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | ) 30 | 31 | const ( 32 | // BootstrapCommand - 33 | BootstrapCommand = "/usr/local/bin/kolla_set_configs && keystone-manage bootstrap" 34 | ) 35 | 36 | // BootstrapJob func 37 | func BootstrapJob( 38 | instance *keystonev1.KeystoneAPI, 39 | labels map[string]string, 40 | annotations map[string]string, 41 | endpoints map[string]string, 42 | memcached *memcachedv1.Memcached, 43 | ) *batchv1.Job { 44 | runAsUser := int64(0) 45 | 46 | args := []string{"-c", BootstrapCommand} 47 | 48 | envVars := map[string]env.Setter{} 49 | envVars["KOLLA_CONFIG_STRATEGY"] = env.SetValue("COPY_ALWAYS") 50 | envVars["KOLLA_BOOTSTRAP"] = env.SetValue("true") 51 | envVars["OS_BOOTSTRAP_USERNAME"] = env.SetValue(instance.Spec.AdminUser) 52 | envVars["OS_BOOTSTRAP_PROJECT_NAME"] = env.SetValue(instance.Spec.AdminProject) 53 | envVars["OS_BOOTSTRAP_SERVICE_NAME"] = env.SetValue(ServiceName) 54 | envVars["OS_BOOTSTRAP_REGION_ID"] = env.SetValue(instance.Spec.Region) 55 | 56 | if _, ok := endpoints["admin"]; ok { 57 | envVars["OS_BOOTSTRAP_ADMIN_URL"] = env.SetValue(endpoints["admin"]) 58 | } 59 | if _, ok := endpoints["internal"]; ok { 60 | envVars["OS_BOOTSTRAP_INTERNAL_URL"] = env.SetValue(endpoints["internal"]) 61 | } 62 | if _, ok := endpoints["public"]; ok { 63 | envVars["OS_BOOTSTRAP_PUBLIC_URL"] = env.SetValue(endpoints["public"]) 64 | } 65 | 66 | // create Volume and VolumeMounts 67 | bootstrapExtraMounts := []keystonev1.KeystoneExtraMounts{} 68 | volumes := getVolumes(instance, bootstrapExtraMounts, BootstrapPropagation) 69 | volumeMounts := getVolumeMounts(bootstrapExtraMounts, BootstrapPropagation) 70 | 71 | // add CA cert if defined 72 | if instance.Spec.TLS.CaBundleSecretName != "" { 73 | volumes = append(volumes, instance.Spec.TLS.CreateVolume()) 74 | volumeMounts = append(volumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) 75 | } 76 | 77 | // add MTLS cert if defined 78 | if memcached.GetMemcachedMTLSSecret() != "" { 79 | volumes = append(volumes, memcached.CreateMTLSVolume()) 80 | volumeMounts = append(volumeMounts, corev1.VolumeMount{ 81 | Name: *memcached.Spec.TLS.MTLS.AuthCertSecret.SecretName, 82 | MountPath: "/etc/pki/tls/certs/mtls.crt", 83 | SubPath: tls.CertKey, 84 | ReadOnly: true, 85 | }, corev1.VolumeMount{ 86 | Name: *memcached.Spec.TLS.MTLS.AuthCertSecret.SecretName, 87 | MountPath: "/etc/pki/tls/private/mtls.key", 88 | SubPath: tls.PrivateKey, 89 | ReadOnly: true, 90 | }, corev1.VolumeMount{ 91 | Name: *memcached.Spec.TLS.MTLS.AuthCertSecret.SecretName, 92 | MountPath: "/etc/pki/tls/certs/mtls-ca.crt", 93 | SubPath: tls.CAKey, 94 | ReadOnly: true, 95 | }) 96 | } 97 | 98 | job := &batchv1.Job{ 99 | ObjectMeta: metav1.ObjectMeta{ 100 | Name: ServiceName + "-bootstrap", 101 | Namespace: instance.Namespace, 102 | Labels: labels, 103 | }, 104 | Spec: batchv1.JobSpec{ 105 | Template: corev1.PodTemplateSpec{ 106 | ObjectMeta: metav1.ObjectMeta{ 107 | Annotations: annotations, 108 | }, 109 | Spec: corev1.PodSpec{ 110 | RestartPolicy: corev1.RestartPolicyOnFailure, 111 | ServiceAccountName: instance.RbacResourceName(), 112 | Containers: []corev1.Container{ 113 | { 114 | Name: ServiceName + "-bootstrap", 115 | Image: instance.Spec.ContainerImage, 116 | Command: []string{ 117 | "/bin/bash", 118 | }, 119 | Args: args, 120 | SecurityContext: &corev1.SecurityContext{ 121 | RunAsUser: &runAsUser, 122 | }, 123 | Env: []corev1.EnvVar{ 124 | { 125 | Name: "OS_BOOTSTRAP_PASSWORD", 126 | ValueFrom: &corev1.EnvVarSource{ 127 | SecretKeyRef: &corev1.SecretKeySelector{ 128 | LocalObjectReference: corev1.LocalObjectReference{ 129 | Name: instance.Spec.Secret, 130 | }, 131 | Key: "AdminPassword", 132 | }, 133 | }, 134 | }, 135 | }, 136 | VolumeMounts: volumeMounts, 137 | }, 138 | }, 139 | Volumes: volumes, 140 | }, 141 | }, 142 | }, 143 | } 144 | job.Spec.Template.Spec.Containers[0].Env = env.MergeEnvs(job.Spec.Template.Spec.Containers[0].Env, envVars) 145 | 146 | if instance.Spec.NodeSelector != nil { 147 | job.Spec.Template.Spec.NodeSelector = *instance.Spec.NodeSelector 148 | } 149 | 150 | return job 151 | } 152 | -------------------------------------------------------------------------------- /api/v1beta1/keystoneservice.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package v1beta1 17 | 18 | import ( 19 | "context" 20 | "fmt" 21 | "time" 22 | 23 | "github.com/openstack-k8s-operators/lib-common/modules/common/condition" 24 | "github.com/openstack-k8s-operators/lib-common/modules/common/helper" 25 | "github.com/openstack-k8s-operators/lib-common/modules/common/util" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 28 | 29 | k8s_errors "k8s.io/apimachinery/pkg/api/errors" 30 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 | "k8s.io/apimachinery/pkg/types" 32 | ctrl "sigs.k8s.io/controller-runtime" 33 | ) 34 | 35 | // KeystoneServiceHelper - 36 | type KeystoneServiceHelper struct { 37 | service *KeystoneService 38 | timeout time.Duration 39 | labels map[string]string 40 | id string 41 | } 42 | 43 | // NewKeystoneService returns an initialized KeystoneService. 44 | func NewKeystoneService( 45 | spec KeystoneServiceSpec, 46 | namespace string, 47 | labels map[string]string, 48 | timeout time.Duration, 49 | ) *KeystoneServiceHelper { 50 | service := &KeystoneService{ 51 | ObjectMeta: metav1.ObjectMeta{ 52 | Name: spec.ServiceName, 53 | Namespace: namespace, 54 | }, 55 | Spec: spec, 56 | } 57 | 58 | return &KeystoneServiceHelper{ 59 | service: service, 60 | timeout: timeout, 61 | labels: labels, 62 | } 63 | } 64 | 65 | // CreateOrPatch - creates or patches a KeystoneService, reconciles after Xs if object won't exist. 66 | func (ks *KeystoneServiceHelper) CreateOrPatch( 67 | ctx context.Context, 68 | h *helper.Helper, 69 | ) (ctrl.Result, error) { 70 | service := &KeystoneService{ 71 | ObjectMeta: metav1.ObjectMeta{ 72 | Name: ks.service.Name, 73 | Namespace: ks.service.Namespace, 74 | }, 75 | } 76 | 77 | // add finalizer 78 | controllerutil.AddFinalizer(service, h.GetFinalizer()) 79 | 80 | op, err := controllerutil.CreateOrPatch(ctx, h.GetClient(), service, func() error { 81 | service.Spec = ks.service.Spec 82 | service.Labels = util.MergeStringMaps(service.Labels, ks.labels) 83 | 84 | return controllerutil.SetControllerReference(h.GetBeforeObject(), service, h.GetScheme()) 85 | }) 86 | if err != nil { 87 | if k8s_errors.IsNotFound(err) { 88 | h.GetLogger().Info(fmt.Sprintf("KeystoneService %s not found, reconcile in %s", service.Name, ks.timeout)) 89 | return ctrl.Result{RequeueAfter: ks.timeout}, nil 90 | } 91 | return ctrl.Result{}, err 92 | } 93 | if op != controllerutil.OperationResultNone { 94 | h.GetLogger().Info(fmt.Sprintf("KeystoneService %s - %s", service.Name, op)) 95 | } 96 | 97 | // update the service object of the KeystoneService type 98 | ks.service, err = GetKeystoneServiceWithName(ctx, h, service.GetName(), service.GetNamespace()) 99 | if err != nil { 100 | return ctrl.Result{}, err 101 | } 102 | 103 | ks.id = ks.service.Status.ServiceID 104 | 105 | return ctrl.Result{}, nil 106 | } 107 | 108 | // GetServiceID - returns the openstack service ID 109 | func (ks *KeystoneServiceHelper) GetServiceID() string { 110 | return ks.id 111 | } 112 | 113 | // GetConditions - returns the conditions of the keystone service object 114 | func (ks *KeystoneServiceHelper) GetConditions() *condition.Conditions { 115 | return &ks.service.Status.Conditions 116 | } 117 | 118 | // Delete - deletes a KeystoneService if it exists. 119 | func (ks *KeystoneServiceHelper) Delete( 120 | ctx context.Context, 121 | h *helper.Helper, 122 | ) error { 123 | 124 | service, err := GetKeystoneServiceWithName(ctx, h, ks.service.Name, ks.service.Namespace) 125 | if err != nil { 126 | if k8s_errors.IsNotFound(err) { 127 | return nil 128 | } 129 | return err 130 | } 131 | 132 | err = h.GetClient().Delete(ctx, service, &client.DeleteOptions{}) 133 | if err != nil && !k8s_errors.IsNotFound(err) { 134 | return err 135 | } 136 | 137 | // Service is deleted so remove the finalizer. 138 | if controllerutil.RemoveFinalizer(service, h.GetFinalizer()) { 139 | err := h.GetClient().Update(ctx, service) 140 | if err != nil && !k8s_errors.IsNotFound(err) { 141 | return err 142 | } 143 | } 144 | 145 | h.GetLogger().Info(fmt.Sprintf("KeystoneService %s in namespace %s deleted", service.Name, service.Namespace)) 146 | 147 | return nil 148 | } 149 | 150 | // GetKeystoneServiceWithName func 151 | func GetKeystoneServiceWithName( 152 | ctx context.Context, 153 | h *helper.Helper, 154 | name string, 155 | namespace string, 156 | ) (*KeystoneService, error) { 157 | 158 | ks := &KeystoneService{} 159 | err := h.GetClient().Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, ks) 160 | if err != nil { 161 | return nil, err 162 | } 163 | 164 | return ks, nil 165 | } 166 | 167 | // DeleteKeystoneServiceWithName func 168 | func DeleteKeystoneServiceWithName( 169 | ctx context.Context, 170 | h *helper.Helper, 171 | name string, 172 | namespace string, 173 | ) error { 174 | 175 | ks, err := GetKeystoneServiceWithName(ctx, h, name, namespace) 176 | if err != nil && !k8s_errors.IsNotFound(err) { 177 | return err 178 | } 179 | 180 | if ks != nil { 181 | ksSvcObj := NewKeystoneService(ks.Spec, namespace, map[string]string{}, 10) 182 | 183 | err = ksSvcObj.Delete(ctx, h) 184 | if err != nil { 185 | return err 186 | } 187 | } 188 | 189 | return nil 190 | } 191 | -------------------------------------------------------------------------------- /test/kuttl/common/assert_sample_deployment.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Check for: 3 | # 4 | # - 1 KeystoneAPI CR 5 | # - Deployment with 1 Pod for KeystoneAPI CR 6 | # - Keystone-internal Service 7 | # - Keystone-public Service 8 | 9 | apiVersion: keystone.openstack.org/v1beta1 10 | kind: KeystoneAPI 11 | metadata: 12 | finalizers: 13 | - openstack.org/keystoneapi 14 | name: keystone 15 | spec: 16 | adminProject: admin 17 | adminUser: admin 18 | customServiceConfig: | 19 | [DEFAULT] 20 | debug = true 21 | databaseInstance: openstack 22 | databaseAccount: keystone 23 | memcachedInstance: memcached 24 | passwordSelectors: 25 | admin: AdminPassword 26 | preserveJobs: false 27 | region: regionOne 28 | replicas: 1 29 | resources: 30 | requests: 31 | cpu: "1" 32 | memory: 500Mi 33 | secret: osp-secret 34 | status: 35 | databaseHostname: openstack.keystone-kuttl-tests.svc 36 | readyCount: 1 37 | --- 38 | apiVersion: apps/v1 39 | kind: Deployment 40 | metadata: 41 | name: keystone 42 | spec: 43 | replicas: 1 44 | template: 45 | metadata: 46 | labels: 47 | service: keystone 48 | spec: 49 | affinity: 50 | podAntiAffinity: 51 | preferredDuringSchedulingIgnoredDuringExecution: 52 | - podAffinityTerm: 53 | labelSelector: 54 | matchExpressions: 55 | - key: service 56 | operator: In 57 | values: 58 | - keystone 59 | topologyKey: kubernetes.io/hostname 60 | weight: 100 61 | containers: 62 | - args: 63 | - -c 64 | - /usr/local/bin/kolla_start 65 | command: 66 | - /bin/bash 67 | imagePullPolicy: IfNotPresent 68 | livenessProbe: 69 | failureThreshold: 3 70 | httpGet: 71 | path: /v3 72 | port: 5000 73 | scheme: HTTP 74 | initialDelaySeconds: 5 75 | periodSeconds: 30 76 | successThreshold: 1 77 | timeoutSeconds: 30 78 | name: keystone-api 79 | readinessProbe: 80 | failureThreshold: 3 81 | httpGet: 82 | path: /v3 83 | port: 5000 84 | scheme: HTTP 85 | initialDelaySeconds: 5 86 | periodSeconds: 30 87 | successThreshold: 1 88 | timeoutSeconds: 30 89 | resources: 90 | requests: 91 | cpu: "1" 92 | memory: 500Mi 93 | restartPolicy: Always 94 | serviceAccount: keystone-keystone 95 | serviceAccountName: keystone-keystone 96 | status: 97 | availableReplicas: 1 98 | replicas: 1 99 | --- 100 | # the openshift annotations can't be checked through the deployment above 101 | apiVersion: v1 102 | kind: Pod 103 | metadata: 104 | annotations: 105 | openshift.io/scc: anyuid 106 | labels: 107 | service: keystone 108 | --- 109 | apiVersion: v1 110 | kind: Service 111 | metadata: 112 | labels: 113 | endpoint: internal 114 | service: keystone 115 | name: keystone-internal 116 | spec: 117 | ports: 118 | - name: keystone-internal 119 | selector: 120 | service: keystone 121 | type: ClusterIP 122 | --- 123 | apiVersion: v1 124 | kind: Service 125 | metadata: 126 | labels: 127 | endpoint: public 128 | service: keystone 129 | name: keystone-public 130 | spec: 131 | ports: 132 | - name: keystone-public 133 | selector: 134 | service: keystone 135 | type: ClusterIP 136 | --- 137 | # the actual addresses of the apiEndpoints are platform specific, so we can't rely on 138 | # kuttl asserts to check them. This short script gathers the addresses and checks that 139 | # the three endpoints are defined and their addresses follow the default pattern 140 | apiVersion: kuttl.dev/v1beta1 141 | kind: TestAssert 142 | commands: 143 | - script: | 144 | # the actual addresses of the apiEndpoints are platform specific, so we can't rely on 145 | # kuttl asserts to check them. This short script gathers the addresses and checks that 146 | # the three endpoints are defined and their addresses follow the default pattern 147 | template='{{.status.apiEndpoints.internal}}{{":"}}{{.status.apiEndpoints.public}}{{"\n"}}' 148 | regex="http:\/\/keystone-internal.$NAMESPACE.*:http:\/\/keystone-public.$NAMESPACE.*" 149 | apiEndpoints=$(oc get -n $NAMESPACE KeystoneAPI keystone -o go-template="$template") 150 | matches=$(echo "$apiEndpoints" | sed -e "s?$regex??") 151 | if [ -z "$matches" ]; then 152 | exit 0 153 | else 154 | echo "Endpoints URLS: $apiEndpoints do not match regex" 155 | exit 1 156 | fi 157 | 158 | # when using image digests the containerImage URLs are SHA's so we verify them with a script 159 | tupleTemplate='{{ range (index .spec.template.spec.containers 0).env }}{{ .name }}{{ "#" }}{{ .value}}{{"\n"}}{{ end }}' 160 | imageTuples=$(oc get -n openstack-operators deployment keystone-operator-controller-manager -o go-template="$tupleTemplate") 161 | # format of imageTuple is: RELATED_IMAGE_KEYSTONE_# separated by newlines 162 | for ITEM in $(echo $imageTuples); do 163 | # it is an image 164 | if echo $ITEM | grep 'RELATED_IMAGE' &> /dev/null; then 165 | NAME=$(echo $ITEM | sed -e 's|^RELATED_IMAGE_KEYSTONE_\([^_]*\)_.*|\1|') 166 | IMG_FROM_ENV=$(echo $ITEM | sed -e 's|^.*#\(.*\)|\1|') 167 | template='{{.spec.containerImage}}' 168 | case $NAME in 169 | API) 170 | SERVICE_IMAGE=$(oc get -n $NAMESPACE keystoneapi keystone -o go-template="$template") 171 | ;; 172 | esac 173 | if [ "$SERVICE_IMAGE" != "$IMG_FROM_ENV" ]; then 174 | echo "$NAME image does not equal $VALUE" 175 | exit 1 176 | fi 177 | fi 178 | done 179 | -------------------------------------------------------------------------------- /api/bases/keystone.openstack.org_keystoneservices.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.18.0 7 | name: keystoneservices.keystone.openstack.org 8 | spec: 9 | group: keystone.openstack.org 10 | names: 11 | kind: KeystoneService 12 | listKind: KeystoneServiceList 13 | plural: keystoneservices 14 | singular: keystoneservice 15 | scope: Namespaced 16 | versions: 17 | - additionalPrinterColumns: 18 | - description: Status 19 | jsonPath: .status.conditions[0].status 20 | name: Status 21 | type: string 22 | - description: Message 23 | jsonPath: .status.conditions[0].message 24 | name: Message 25 | type: string 26 | name: v1beta1 27 | schema: 28 | openAPIV3Schema: 29 | description: KeystoneService is the Schema for the keystoneservices API 30 | properties: 31 | apiVersion: 32 | description: |- 33 | APIVersion defines the versioned schema of this representation of an object. 34 | Servers should convert recognized schemas to the latest internal value, and 35 | may reject unrecognized values. 36 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 37 | type: string 38 | kind: 39 | description: |- 40 | Kind is a string value representing the REST resource this object represents. 41 | Servers may infer this from the endpoint the client submits requests to. 42 | Cannot be updated. 43 | In CamelCase. 44 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 45 | type: string 46 | metadata: 47 | type: object 48 | spec: 49 | description: KeystoneServiceSpec defines the desired state of KeystoneService 50 | properties: 51 | enabled: 52 | description: Enabled - whether or not the service is enabled. 53 | type: boolean 54 | passwordSelector: 55 | description: PasswordSelector - Selector to get the ServiceUser password 56 | from the Secret, e.g. PlacementPassword 57 | type: string 58 | secret: 59 | description: Secret containing OpenStack password information for 60 | the ServiceUser 61 | type: string 62 | serviceDescription: 63 | description: ServiceDescription - Description for the service. 64 | type: string 65 | serviceName: 66 | description: ServiceName - Name of the service. 67 | type: string 68 | serviceType: 69 | description: ServiceType - Type is the type of the service. 70 | type: string 71 | serviceUser: 72 | description: ServiceUser - optional username used for this service 73 | type: string 74 | required: 75 | - enabled 76 | - passwordSelector 77 | - secret 78 | - serviceName 79 | - serviceType 80 | - serviceUser 81 | type: object 82 | status: 83 | description: KeystoneServiceStatus defines the observed state of KeystoneService 84 | properties: 85 | conditions: 86 | description: Conditions 87 | items: 88 | description: Condition defines an observation of a API resource 89 | operational state. 90 | properties: 91 | lastTransitionTime: 92 | description: |- 93 | Last time the condition transitioned from one status to another. 94 | This should be when the underlying condition changed. If that is not known, then using the time when 95 | the API field changed is acceptable. 96 | format: date-time 97 | type: string 98 | message: 99 | description: A human readable message indicating details about 100 | the transition. 101 | type: string 102 | reason: 103 | description: The reason for the condition's last transition 104 | in CamelCase. 105 | type: string 106 | severity: 107 | description: |- 108 | Severity provides a classification of Reason code, so the current situation is immediately 109 | understandable and could act accordingly. 110 | It is meant for situations where Status=False and it should be indicated if it is just 111 | informational, warning (next reconciliation might fix it) or an error (e.g. DB create issue 112 | and no actions to automatically resolve the issue can/should be done). 113 | For conditions where Status=Unknown or Status=True the Severity should be SeverityNone. 114 | type: string 115 | status: 116 | description: Status of the condition, one of True, False, Unknown. 117 | type: string 118 | type: 119 | description: Type of condition in CamelCase. 120 | type: string 121 | required: 122 | - lastTransitionTime 123 | - status 124 | - type 125 | type: object 126 | type: array 127 | serviceID: 128 | type: string 129 | type: object 130 | type: object 131 | served: true 132 | storage: true 133 | subresources: 134 | status: {} 135 | -------------------------------------------------------------------------------- /config/crd/bases/keystone.openstack.org_keystoneservices.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.18.0 7 | name: keystoneservices.keystone.openstack.org 8 | spec: 9 | group: keystone.openstack.org 10 | names: 11 | kind: KeystoneService 12 | listKind: KeystoneServiceList 13 | plural: keystoneservices 14 | singular: keystoneservice 15 | scope: Namespaced 16 | versions: 17 | - additionalPrinterColumns: 18 | - description: Status 19 | jsonPath: .status.conditions[0].status 20 | name: Status 21 | type: string 22 | - description: Message 23 | jsonPath: .status.conditions[0].message 24 | name: Message 25 | type: string 26 | name: v1beta1 27 | schema: 28 | openAPIV3Schema: 29 | description: KeystoneService is the Schema for the keystoneservices API 30 | properties: 31 | apiVersion: 32 | description: |- 33 | APIVersion defines the versioned schema of this representation of an object. 34 | Servers should convert recognized schemas to the latest internal value, and 35 | may reject unrecognized values. 36 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 37 | type: string 38 | kind: 39 | description: |- 40 | Kind is a string value representing the REST resource this object represents. 41 | Servers may infer this from the endpoint the client submits requests to. 42 | Cannot be updated. 43 | In CamelCase. 44 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 45 | type: string 46 | metadata: 47 | type: object 48 | spec: 49 | description: KeystoneServiceSpec defines the desired state of KeystoneService 50 | properties: 51 | enabled: 52 | description: Enabled - whether or not the service is enabled. 53 | type: boolean 54 | passwordSelector: 55 | description: PasswordSelector - Selector to get the ServiceUser password 56 | from the Secret, e.g. PlacementPassword 57 | type: string 58 | secret: 59 | description: Secret containing OpenStack password information for 60 | the ServiceUser 61 | type: string 62 | serviceDescription: 63 | description: ServiceDescription - Description for the service. 64 | type: string 65 | serviceName: 66 | description: ServiceName - Name of the service. 67 | type: string 68 | serviceType: 69 | description: ServiceType - Type is the type of the service. 70 | type: string 71 | serviceUser: 72 | description: ServiceUser - optional username used for this service 73 | type: string 74 | required: 75 | - enabled 76 | - passwordSelector 77 | - secret 78 | - serviceName 79 | - serviceType 80 | - serviceUser 81 | type: object 82 | status: 83 | description: KeystoneServiceStatus defines the observed state of KeystoneService 84 | properties: 85 | conditions: 86 | description: Conditions 87 | items: 88 | description: Condition defines an observation of a API resource 89 | operational state. 90 | properties: 91 | lastTransitionTime: 92 | description: |- 93 | Last time the condition transitioned from one status to another. 94 | This should be when the underlying condition changed. If that is not known, then using the time when 95 | the API field changed is acceptable. 96 | format: date-time 97 | type: string 98 | message: 99 | description: A human readable message indicating details about 100 | the transition. 101 | type: string 102 | reason: 103 | description: The reason for the condition's last transition 104 | in CamelCase. 105 | type: string 106 | severity: 107 | description: |- 108 | Severity provides a classification of Reason code, so the current situation is immediately 109 | understandable and could act accordingly. 110 | It is meant for situations where Status=False and it should be indicated if it is just 111 | informational, warning (next reconciliation might fix it) or an error (e.g. DB create issue 112 | and no actions to automatically resolve the issue can/should be done). 113 | For conditions where Status=Unknown or Status=True the Severity should be SeverityNone. 114 | type: string 115 | status: 116 | description: Status of the condition, one of True, False, Unknown. 117 | type: string 118 | type: 119 | description: Type of condition in CamelCase. 120 | type: string 121 | required: 122 | - lastTransitionTime 123 | - status 124 | - type 125 | type: object 126 | type: array 127 | serviceID: 128 | type: string 129 | type: object 130 | type: object 131 | served: true 132 | storage: true 133 | subresources: 134 | status: {} 135 | -------------------------------------------------------------------------------- /test/functional/base_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package functional_test 18 | 19 | import ( 20 | "fmt" 21 | 22 | . "github.com/onsi/gomega" //revive:disable:dot-imports 23 | 24 | batchv1 "k8s.io/api/batch/v1" 25 | corev1 "k8s.io/api/core/v1" 26 | "k8s.io/apimachinery/pkg/types" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | 29 | keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" 30 | keystone_base "github.com/openstack-k8s-operators/keystone-operator/internal/keystone" 31 | condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" 32 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 | ) 34 | 35 | func GetKeystoneAPISpec(fernetMaxKeys int32) map[string]any { 36 | return map[string]any{ 37 | "databaseInstance": "openstack", 38 | "replicas": 1, 39 | "secret": SecretName, 40 | "databaseAccount": AccountName, 41 | "fernetMaxActiveKeys": fernetMaxKeys, 42 | } 43 | } 44 | 45 | func GetDefaultKeystoneAPISpec() map[string]any { 46 | return GetKeystoneAPISpec(5) 47 | } 48 | 49 | func GetTLSKeystoneAPISpec() map[string]any { 50 | return map[string]any{ 51 | "databaseInstance": "openstack", 52 | "replicas": 1, 53 | "secret": SecretName, 54 | "databaseAccount": AccountName, 55 | "tls": map[string]any{ 56 | "api": map[string]any{ 57 | "internal": map[string]any{ 58 | "secretName": InternalCertSecretName, 59 | }, 60 | "public": map[string]any{ 61 | "secretName": PublicCertSecretName, 62 | }, 63 | }, 64 | "caBundleSecretName": CABundleSecretName, 65 | }, 66 | } 67 | } 68 | 69 | func CreateKeystoneAPI(name types.NamespacedName, spec map[string]any) client.Object { 70 | 71 | raw := map[string]any{ 72 | "apiVersion": "keystone.openstack.org/v1beta1", 73 | "kind": "KeystoneAPI", 74 | "metadata": map[string]any{ 75 | "name": name.Name, 76 | "namespace": name.Namespace, 77 | }, 78 | "spec": spec, 79 | } 80 | return th.CreateUnstructured(raw) 81 | } 82 | 83 | func GetKeystoneAPI(name types.NamespacedName) *keystonev1.KeystoneAPI { 84 | instance := &keystonev1.KeystoneAPI{} 85 | Eventually(func(g Gomega) { 86 | g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed()) 87 | }, timeout, interval).Should(Succeed()) 88 | return instance 89 | } 90 | 91 | func CreateKeystoneAPISecret(namespace string, name string) *corev1.Secret { 92 | return th.CreateSecret( 93 | types.NamespacedName{Namespace: namespace, Name: name}, 94 | map[string][]byte{ 95 | "AdminPassword": []byte("12345678"), 96 | }, 97 | ) 98 | } 99 | 100 | func KeystoneConditionGetter(name types.NamespacedName) condition.Conditions { 101 | instance := GetKeystoneAPI(name) 102 | return instance.Status.Conditions 103 | } 104 | 105 | func GetCronJob(name types.NamespacedName) *batchv1.CronJob { 106 | instance := &batchv1.CronJob{} 107 | Eventually(func(g Gomega) { 108 | g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed()) 109 | }, timeout, interval).Should(Succeed()) 110 | return instance 111 | } 112 | 113 | func CreateKeystoneMessageBusSecret(namespace string, name string) *corev1.Secret { 114 | s := th.CreateSecret( 115 | types.NamespacedName{Namespace: namespace, Name: name}, 116 | map[string][]byte{ 117 | "transport_url": fmt.Appendf(nil, "rabbit://%s/fake", name), 118 | }, 119 | ) 120 | logger.Info("Secret created", "name", name) 121 | return s 122 | } 123 | 124 | // GetSampleTopologySpec - A sample (and opinionated) Topology Spec used to 125 | // test KeystoneAPI 126 | // Note this is just an example that should not be used in production for 127 | // multiple reasons: 128 | // 1. It uses ScheduleAnyway as strategy, which is something we might 129 | // want to avoid by default 130 | // 2. Usually a topologySpreadConstraints is used to take care about 131 | // multi AZ, which is not applicable in this context 132 | func GetSampleTopologySpec(label string) (map[string]any, []corev1.TopologySpreadConstraint) { 133 | // Build the topology Spec 134 | topologySpec := map[string]any{ 135 | "topologySpreadConstraints": []map[string]any{ 136 | { 137 | "maxSkew": 1, 138 | "topologyKey": corev1.LabelHostname, 139 | "whenUnsatisfiable": "ScheduleAnyway", 140 | "labelSelector": map[string]any{ 141 | "matchLabels": map[string]any{ 142 | "service": keystone_base.ServiceName, 143 | "component": label, 144 | }, 145 | }, 146 | }, 147 | }, 148 | } 149 | // Build the topologyObj representation 150 | topologySpecObj := []corev1.TopologySpreadConstraint{ 151 | { 152 | MaxSkew: 1, 153 | TopologyKey: corev1.LabelHostname, 154 | WhenUnsatisfiable: corev1.ScheduleAnyway, 155 | LabelSelector: &metav1.LabelSelector{ 156 | MatchLabels: map[string]string{ 157 | "service": keystone_base.ServiceName, 158 | "component": label, 159 | }, 160 | }, 161 | }, 162 | } 163 | return topologySpec, topologySpecObj 164 | } 165 | 166 | // GetExtraMounts - Utility function that simulates extraMounts pointing 167 | // to a secret 168 | func GetExtraMounts(kemName string, kemPath string) []map[string]any { 169 | return []map[string]any{ 170 | { 171 | "name": kemName, 172 | "region": "az0", 173 | "extraVol": []map[string]any{ 174 | { 175 | "extraVolType": kemName, 176 | "propagation": []string{ 177 | "Keystone", 178 | }, 179 | "volumes": []map[string]any{ 180 | { 181 | "name": kemName, 182 | "secret": map[string]any{ 183 | "secretName": kemName, 184 | }, 185 | }, 186 | }, 187 | "mounts": []map[string]any{ 188 | { 189 | "name": kemName, 190 | "mountPath": kemPath, 191 | "readOnly": true, 192 | }, 193 | }, 194 | }, 195 | }, 196 | }, 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /internal/webhook/v1beta1/keystoneapi_webhook.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1beta1 contains webhook implementations for KeystoneAPI v1beta1 resources. 18 | package v1beta1 19 | 20 | import ( 21 | "context" 22 | "errors" 23 | "fmt" 24 | 25 | "k8s.io/apimachinery/pkg/runtime" 26 | ctrl "sigs.k8s.io/controller-runtime" 27 | logf "sigs.k8s.io/controller-runtime/pkg/log" 28 | "sigs.k8s.io/controller-runtime/pkg/webhook" 29 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 30 | 31 | keystonev1beta1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" 32 | ) 33 | 34 | // Static errors for type assertions 35 | var ( 36 | errUnexpectedObjectType = errors.New("unexpected object type") 37 | ) 38 | 39 | // nolint:unused 40 | // log is for logging in this package. 41 | var keystoneapilog = logf.Log.WithName("keystoneapi-resource") 42 | 43 | // SetupKeystoneAPIWebhookWithManager registers the webhook for KeystoneAPI in the manager. 44 | func SetupKeystoneAPIWebhookWithManager(mgr ctrl.Manager) error { 45 | return ctrl.NewWebhookManagedBy(mgr).For(&keystonev1beta1.KeystoneAPI{}). 46 | WithValidator(&KeystoneAPICustomValidator{}). 47 | WithDefaulter(&KeystoneAPICustomDefaulter{}). 48 | Complete() 49 | } 50 | 51 | // TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 52 | 53 | // +kubebuilder:webhook:path=/mutate-keystone-openstack-org-v1beta1-keystoneapi,mutating=true,failurePolicy=fail,sideEffects=None,groups=keystone.openstack.org,resources=keystoneapis,verbs=create;update,versions=v1beta1,name=mkeystoneapi-v1beta1.kb.io,admissionReviewVersions=v1 54 | 55 | // KeystoneAPICustomDefaulter struct is responsible for setting default values on the custom resource of the 56 | // Kind KeystoneAPI when those are created or updated. 57 | // 58 | // NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods, 59 | // as it is used only for temporary operations and does not need to be deeply copied. 60 | type KeystoneAPICustomDefaulter struct { 61 | // TODO(user): Add more fields as needed for defaulting 62 | } 63 | 64 | var _ webhook.CustomDefaulter = &KeystoneAPICustomDefaulter{} 65 | 66 | // Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind KeystoneAPI. 67 | func (d *KeystoneAPICustomDefaulter) Default(_ context.Context, obj runtime.Object) error { 68 | keystoneapi, ok := obj.(*keystonev1beta1.KeystoneAPI) 69 | 70 | if !ok { 71 | return fmt.Errorf("%w: expected an KeystoneAPI object but got %T", errUnexpectedObjectType, obj) 72 | } 73 | keystoneapilog.Info("Defaulting for KeystoneAPI", "name", keystoneapi.GetName()) 74 | 75 | // Call the defaulting logic from api/v1beta1 76 | keystoneapi.Default() 77 | 78 | return nil 79 | } 80 | 81 | // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. 82 | // NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here. 83 | // Modifying the path for an invalid path can cause API server errors; failing to locate the webhook. 84 | // +kubebuilder:webhook:path=/validate-keystone-openstack-org-v1beta1-keystoneapi,mutating=false,failurePolicy=fail,sideEffects=None,groups=keystone.openstack.org,resources=keystoneapis,verbs=create;update,versions=v1beta1,name=vkeystoneapi-v1beta1.kb.io,admissionReviewVersions=v1 85 | 86 | // KeystoneAPICustomValidator struct is responsible for validating the KeystoneAPI resource 87 | // when it is created, updated, or deleted. 88 | // 89 | // NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods, 90 | // as this struct is used only for temporary operations and does not need to be deeply copied. 91 | type KeystoneAPICustomValidator struct { 92 | // TODO(user): Add more fields as needed for validation 93 | } 94 | 95 | var _ webhook.CustomValidator = &KeystoneAPICustomValidator{} 96 | 97 | // ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type KeystoneAPI. 98 | func (v *KeystoneAPICustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { 99 | keystoneapi, ok := obj.(*keystonev1beta1.KeystoneAPI) 100 | if !ok { 101 | return nil, fmt.Errorf("%w: expected a KeystoneAPI object but got %T", errUnexpectedObjectType, obj) 102 | } 103 | keystoneapilog.Info("Validation for KeystoneAPI upon creation", "name", keystoneapi.GetName()) 104 | 105 | // Call the validation logic from api/v1beta1 106 | return keystoneapi.ValidateCreate() 107 | } 108 | 109 | // ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type KeystoneAPI. 110 | func (v *KeystoneAPICustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { 111 | keystoneapi, ok := newObj.(*keystonev1beta1.KeystoneAPI) 112 | if !ok { 113 | return nil, fmt.Errorf("%w: expected a KeystoneAPI object for the newObj but got %T", errUnexpectedObjectType, newObj) 114 | } 115 | keystoneapilog.Info("Validation for KeystoneAPI upon update", "name", keystoneapi.GetName()) 116 | 117 | // Call the validation logic from api/v1beta1 118 | return keystoneapi.ValidateUpdate(oldObj) 119 | } 120 | 121 | // ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type KeystoneAPI. 122 | func (v *KeystoneAPICustomValidator) ValidateDelete(_ context.Context, obj runtime.Object) (admission.Warnings, error) { 123 | keystoneapi, ok := obj.(*keystonev1beta1.KeystoneAPI) 124 | if !ok { 125 | return nil, fmt.Errorf("%w: expected a KeystoneAPI object but got %T", errUnexpectedObjectType, obj) 126 | } 127 | keystoneapilog.Info("Validation for KeystoneAPI upon deletion", "name", keystoneapi.GetName()) 128 | 129 | // Call the validation logic from api/v1beta1 130 | return keystoneapi.ValidateDelete() 131 | } 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # keystone-operator 2 | 3 | A Kubernetes Operator built using the [Operator Framework](https://github.com/operator-framework) for Go. The Operator provides a way to easily install and manage an OpenStack Keystone installation 4 | on Kubernetes. This Operator was developed using [TCIB](https://github.com/openstack-k8s-operators/tcib/blob/main/container-images/containers.yaml) containers for OpenStack. 5 | 6 | # Deployment 7 | 8 | The operator is intended to be deployed via OLM [Operator Lifecycle Manager](https://github.com/operator-framework/operator-lifecycle-manager) 9 | 10 | # API Example 11 | 12 | The Operator creates a custom KeystoneAPI resource that can be used to create Keystone API 13 | instances within the cluster. Example CR to create an Keystone API in your cluster: 14 | 15 | ```yaml 16 | apiVersion: keystone.openstack.org/v1beta1 17 | kind: KeystoneAPI 18 | metadata: 19 | name: keystone 20 | spec: 21 | containerImage: quay.io/podified-antelope-centos9/openstack-keystone:current-podified 22 | replicas: 1 23 | secret: osp-secret 24 | ``` 25 | 26 | ## Example: configure Keystone with additional networks 27 | 28 | The Keystone spec can be used to configure Keystone to have the pods 29 | being attached to additional networks. 30 | 31 | Create a network-attachement-definition which then can be referenced 32 | from the Keystone API CR. 33 | 34 | ``` 35 | --- 36 | apiVersion: k8s.cni.cncf.io/v1 37 | kind: NetworkAttachmentDefinition 38 | metadata: 39 | name: storage 40 | namespace: openstack 41 | spec: 42 | config: | 43 | { 44 | "cniVersion": "0.3.1", 45 | "name": "storage", 46 | "type": "macvlan", 47 | "master": "enp7s0.21", 48 | "ipam": { 49 | "type": "whereabouts", 50 | "range": "172.18.0.0/24", 51 | "range_start": "172.18.0.50", 52 | "range_end": "172.18.0.100" 53 | } 54 | } 55 | ``` 56 | 57 | The following represents an example of Keystone resource that can be used 58 | to trigger the service deployment, and have the service pods attached to 59 | the storage network using the above NetworkAttachmentDefinition. 60 | 61 | ``` 62 | apiVersion: keystone.openstack.org/v1beta1 63 | kind: KeystoneAPI 64 | metadata: 65 | name: keystone 66 | spec: 67 | ... 68 | networkAttachents: 69 | - storage 70 | ... 71 | ``` 72 | 73 | When the service is up and running, it will now have an additional nic 74 | configured for the storage network: 75 | 76 | ``` 77 | # oc rsh keystone-75f5cd6595-kpfr2 78 | sh-5.1# ip a 79 | 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 80 | link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 81 | inet 127.0.0.1/8 scope host lo 82 | valid_lft forever preferred_lft forever 83 | inet6 ::1/128 scope host 84 | valid_lft forever preferred_lft forever 85 | 3: eth0@if298: mtu 1450 qdisc noqueue state UP group default 86 | link/ether 0a:58:0a:82:01:18 brd ff:ff:ff:ff:ff:ff link-netnsid 0 87 | inet 10.130.1.24/23 brd 10.130.1.255 scope global eth0 88 | valid_lft forever preferred_lft forever 89 | inet6 fe80::4cf2:a3ff:feb0:932/64 scope link 90 | valid_lft forever preferred_lft forever 91 | 4: net1@if26: mtu 1500 qdisc noqueue state UP group default 92 | link/ether a2:f1:3b:12:fd:be brd ff:ff:ff:ff:ff:ff link-netnsid 0 93 | inet 172.18.0.52/24 brd 172.18.0.255 scope global net1 94 | valid_lft forever preferred_lft forever 95 | inet6 fe80::a0f1:3bff:fe12:fdbe/64 scope link 96 | valid_lft forever preferred_lft forever 97 | ``` 98 | 99 | ## Example: expose Keystone to an isolated network 100 | 101 | The Keystone spec can be used to configure Keystone to register e.g. 102 | the internal endpoint to an isolated network. MetalLB is used for this 103 | scenario. 104 | 105 | As a pre requisite, MetalLB needs to be installed and worker nodes 106 | prepared to work as MetalLB nodes to serve the LoadBalancer service. 107 | 108 | In this example the following MetalLB IPAddressPool is used: 109 | 110 | ``` 111 | --- 112 | apiVersion: metallb.io/v1beta1 113 | kind: IPAddressPool 114 | metadata: 115 | name: osp-internalapi 116 | namespace: metallb-system 117 | spec: 118 | addresses: 119 | - 172.17.0.200-172.17.0.210 120 | autoAssign: false 121 | ``` 122 | 123 | The following represents an example of Keystone resource that can be used 124 | to trigger the service deployment, and have the internal keystoneAPI endpoint 125 | registerd as a MetalLB service using the IPAddressPool `osp-internal`, 126 | request to use the IP `172.17.0.202` as the VIP and the IP is shared with 127 | other services. 128 | 129 | ``` 130 | apiVersion: keystone.openstack.org/v1beta1 131 | kind: KeystoneAPI 132 | metadata: 133 | name: keystone 134 | spec: 135 | ... 136 | externalEndpoints: 137 | - endpoint: internal 138 | ipAddressPool: osp-internalapi 139 | loadBalancerIPs: 140 | - 172.17.0.202 141 | sharedIP: true 142 | sharedIPKey: "" 143 | ... 144 | ... 145 | ``` 146 | 147 | The internal keystone endpoint gets registered with its service name. This 148 | service name needs to resolve to the `LoadBalancerIP` on the isolated network 149 | either by DNS or via /etc/hosts: 150 | 151 | ``` 152 | # openstack endpoint list -c 'Service Name' -c Interface -c URL --service keystone 153 | +--------------+-----------+-----------------------------------------------------------------+ 154 | | Service Name | Interface | URL | 155 | +--------------+-----------+-----------------------------------------------------------------+ 156 | | keystone | public | http://keystone-public-openstack.apps.ostest.test.metalkube.org | 157 | | keystone | internal | http://keystone-internal.openstack.svc:5000 | 158 | +--------------+-----------+-----------------------------------------------------------------+ 159 | ``` 160 | 161 | # Design 162 | The current design takes care of the following: 163 | 164 | - Creates keystone config files via config maps 165 | - Creates a keystone deployment with the specified replicas 166 | - Creates a keystone service 167 | - Generates Fernet keys (TODO: rotate them, and bounce the APIs upon rotation) 168 | - Keystone bootstrap, and db sync are executed automatically on install and updates 169 | - ConfigMap is recreated on any changes KeystoneAPI object changes and the Deployment updated. 170 | -------------------------------------------------------------------------------- /api/bases/keystone.openstack.org_keystoneendpoints.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.18.0 7 | name: keystoneendpoints.keystone.openstack.org 8 | spec: 9 | group: keystone.openstack.org 10 | names: 11 | kind: KeystoneEndpoint 12 | listKind: KeystoneEndpointList 13 | plural: keystoneendpoints 14 | singular: keystoneendpoint 15 | scope: Namespaced 16 | versions: 17 | - additionalPrinterColumns: 18 | - description: Status 19 | jsonPath: .status.conditions[0].status 20 | name: Status 21 | type: string 22 | - description: Message 23 | jsonPath: .status.conditions[0].message 24 | name: Message 25 | type: string 26 | name: v1beta1 27 | schema: 28 | openAPIV3Schema: 29 | description: KeystoneEndpoint is the Schema for the keystoneendpoints API 30 | properties: 31 | apiVersion: 32 | description: |- 33 | APIVersion defines the versioned schema of this representation of an object. 34 | Servers should convert recognized schemas to the latest internal value, and 35 | may reject unrecognized values. 36 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 37 | type: string 38 | kind: 39 | description: |- 40 | Kind is a string value representing the REST resource this object represents. 41 | Servers may infer this from the endpoint the client submits requests to. 42 | Cannot be updated. 43 | In CamelCase. 44 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 45 | type: string 46 | metadata: 47 | type: object 48 | spec: 49 | description: KeystoneEndpointSpec defines the desired state of KeystoneEndpoint 50 | properties: 51 | endpoints: 52 | additionalProperties: 53 | type: string 54 | description: Endpoints - map with service api endpoint URLs with the 55 | endpoint type as index 56 | type: object 57 | serviceName: 58 | description: ServiceName - Name of the service to create the endpoint 59 | for 60 | type: string 61 | required: 62 | - endpoints 63 | - serviceName 64 | type: object 65 | status: 66 | description: KeystoneEndpointStatus defines the observed state of KeystoneEndpoint 67 | properties: 68 | conditions: 69 | description: Conditions 70 | items: 71 | description: Condition defines an observation of a API resource 72 | operational state. 73 | properties: 74 | lastTransitionTime: 75 | description: |- 76 | Last time the condition transitioned from one status to another. 77 | This should be when the underlying condition changed. If that is not known, then using the time when 78 | the API field changed is acceptable. 79 | format: date-time 80 | type: string 81 | message: 82 | description: A human readable message indicating details about 83 | the transition. 84 | type: string 85 | reason: 86 | description: The reason for the condition's last transition 87 | in CamelCase. 88 | type: string 89 | severity: 90 | description: |- 91 | Severity provides a classification of Reason code, so the current situation is immediately 92 | understandable and could act accordingly. 93 | It is meant for situations where Status=False and it should be indicated if it is just 94 | informational, warning (next reconciliation might fix it) or an error (e.g. DB create issue 95 | and no actions to automatically resolve the issue can/should be done). 96 | For conditions where Status=Unknown or Status=True the Severity should be SeverityNone. 97 | type: string 98 | status: 99 | description: Status of the condition, one of True, False, Unknown. 100 | type: string 101 | type: 102 | description: Type of condition in CamelCase. 103 | type: string 104 | required: 105 | - lastTransitionTime 106 | - status 107 | - type 108 | type: object 109 | type: array 110 | endpointIDs: 111 | additionalProperties: 112 | type: string 113 | type: object 114 | endpoints: 115 | description: Endpoints - current status of latest configured endpoints 116 | for the service 117 | items: 118 | description: Endpoint - 119 | properties: 120 | id: 121 | description: ID - endpoint id 122 | type: string 123 | interface: 124 | description: Interface - public, internal, admin 125 | type: string 126 | url: 127 | description: URL - endpoint url 128 | type: string 129 | required: 130 | - id 131 | - interface 132 | - url 133 | type: object 134 | type: array 135 | observedGeneration: 136 | description: ObservedGeneration - the most recent generation observed 137 | for this service. If the observed generation is less than the spec 138 | generation, then the controller has not processed the latest changes. 139 | format: int64 140 | type: integer 141 | serviceID: 142 | type: string 143 | type: object 144 | type: object 145 | served: true 146 | storage: true 147 | subresources: 148 | status: {} 149 | --------------------------------------------------------------------------------