Certificate management within the openstack-operator is by default done using cert-manager. For each component the openstack-operator creates one or multiple certificates and pass the name/names of the k8s secret, which contains the certificate, key and ca certificate, to the service component. By default cert-manager does not delete k8s secrets, holding the certificate information, when the cert-manager certificate resource gets deleted. Consult the OpenShift documentation on Deleting a TLS secret automatically upon Certificate removal.
In the context of TLS (Transport Layer Security) configuration within the OpenStackControlPlane, two primary levels of traffic encryption are distinguished: ingress
and podLevel
:
ingress
refers to the public (external) traffic that enters the cluster via a routepodLevel
refers to the internal traffic within the cluster, specifically, the communication between pods, services and edpm nodes.
TLS is enabled by default for both ingress
and podLevel
communication.
In terms of how traffic is managed within the cluster:
- Public Endpoints
- Public (external) endpoints are by default terminated at the route. The openstack-operator is responsible for creating these routes for each of the services
- Using the
serviceOverride
it is possible to use aLoadBalancer
service for public endpoints as well.
- Internal Endpoints
- Internal endpoints terminate at the designated service, this can be either
ClusterIP
orLoadBalancer
type of service (e.g. MetalLB).
- Internal endpoints terminate at the designated service, this can be either
More details on networking check Networking
The default TLS settings can be changed using the tls
section of the OpenStackControlPlane
:
Example of a basic TLS section:
apiVersion: core.openstack.org/v1beta1
kind: OpenStackControlPlane
metadata:
name: myctlplane
spec:
tls:
ingress:
enabled: true
podLevel:
enabled: false
caBundleSecretName: myAdditionalCACerts
By default, the openstack-operator provisions the following CAs via cert-manager for securing endpoints:
- rootca-public - used to issue certificates for routes and k8s services for public endpoint httpd vhost configuration.
- rootca-internal - used to issue certificates for all internal communication, like internal endpoint httpd vhost config, database, rabbitmq ..., except of the bellow special use case CAs.
- rootca-ovn - used to issue certificates for ovn/ovs services specifically.
- rootca-libvirt - used to issue certificates for libvirt/qemu services specifically.
By default CA certificates are valid for 10 years and service certificates for 5. This can be customized using the duration
and renewBefore
parameters for CA and certificates, e.g.:
spec:
tls:
ingress:
ca:
duration: 43800h
cert:
duration: 5000h
renewBefore: 100h
enabled: true
podLevel:
enabled: true
internal:
ca:
duration: 43800h
cert:
duration: 5000h
renewBefore: 200h
libvirt:
ca:
duration: 83800h
cert:
duration: 5000h
ovn:
ca:
duration: 83800h
cert:
duration: 5000h
The combined-ca-bundle secret, generated by the openstack-operator, aggregates:
- System default CAs,
- Operator-created CAs,
- Third-party CAs provided with
tls.caBundleSecretName
. This bundle ensures a trusted CA pool is available for all services, supporting both default and custom certificate validation. The bundle can be used to be direct mounted into a deployment to the environment wide CA location. The lib-common common module tls package provides funtionality for this.
The secret also has a default label which can be used to query:
labels:
combined-ca-bundle: ""
Using the global caBundleSecretName
parameter a secret can be referenced containing any additional CA certificates, which should be added to the combined CA bundle:
apiVersion: core.openstack.org/v1beta1
kind: OpenStackControlPlane
metadata:
name: myctlplane
spec:
...
tls:
caBundleSecretName: mycasecret
Multiple entries are allowed in one secret. Whenever openstack-operator
reconciles it will check for expired or new CA certs to be added to the bundle. Not expired CAs will be kept in the secret if they are not yet expired, even if they got removed from the source it was originally taken from. This allows to rotate certs for a service using a new CA.
Note: The CA provided via the apiOverride.route
is right now not added to the CA bundle.
Certificates for both public and internal service endpoints are automatically managed by cert-manager, but can be customized for specific requirements.
Certificates are created for each public endpoint by default. However, custom certificates can be specified by creating a secret with the following keys:
tls.crt
: The TLS certificate,tls.key
: The corresponding private key for the TLS certificate,ca.crt
: The CA certificate that has issued the TLS certificate,
The information is then used to be set the Route
for this public endpoint or in future if a LoadBalancer
service is used to terminated the endpoint at the pod level.
- An alternative would be to directly use the
RouteOverride
to set the cert, key and CA cert for the endpoint.
Example using the route override:
apiVersion: core.openstack.org/v1beta1
kind: OpenStackControlPlane
metadata:
name: myctlplane
spec:
...
keystone:
apiOverride:
route:
spec:
tls:
termination: edge
certificate: |-
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
key: |-
-----BEGIN RSA PRIVATE KEY----
-----END RSA PRIVATE KEY-----
caCertificate: |-
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
insecureEdgeTerminationPolicy: Redirect
For public endpoints, custom certificates can be specified within the apiOverride
section for each service. This is not part of the global TLS configuration but is specific to each service that requires a custom public endpoint certificate.
Example of specifying custom certificates for ingress
traffic:
apiVersion: core.openstack.org/v1beta1
kind: OpenStackControlPlane
metadata:
name: custom-ingress-cert-example
spec:
...
keystone:
apiOverride:
tls:
secretName: custom-ingress-cert-secret
template:
...
For internal TLS, certificates are similarly managed with options for customization. CertManager issues certificates based on internal or public issuers, and the resulting secrets can be integrated into service configurations. To specify a custom certificates for internal endpoints, it's only possible providing the custom issuer.
The lib-common common module tls package defines several structs to organize and apply TLS settings for different service types within the OpenStack deployment.
The GenericService
represents the basic configuration for a service requiring TLS. It includes the SecretName field, which refers to a Kubernetes secret containing the TLS certificate (tls.crt),private key (tls.key) and the CA certificate (ca.crt) used to sign the service certificate.
The SimpleService
extends the GenericService by including CA information through embedding the Ca
struct. This allows for specifying a custom CA bundle secret caBundleSecretName
.
Example of a single, not endpoint specific service:
tls:
caBundleSecretName: combined-ca-bundle
secretName: cert-nova-novncproxy-cell1-public-svc
The API
struct focuses on services that provide an API, which might have different TLS requirements for its public and internal endpoints. It includes:
APIService
: Encapsulates TLS configurations for both public and internal API endpoints.Public
: A GenericService for the public (external) endpoint of the API.Internal
: A GenericService for the internal endpoint of the API.
Example of a public/internal API service:
tls:
api:
# secret holding tls.crt and tls.key for the APIs internal k8s service
internal:
secretName: cert-internal-svc
# secret holding tls.crt and tls.key for the APIs public k8s service
public:
secretName: cert-public-svc
# secret holding the tls-ca-bundle.pem to be used as a deploymend env CA bundle
caBundleSecretName: combined-ca-bundle
As mentioned above - certificates, including TLS certificates (tls.crt
), private keys (tls.key
), and CA certificates (ca.crt
), are stored in secrets. These secrets are mounted into the containers of the services that require them. Depending on how the service gets started (using kolla or not), the cert and key get either mounted to a directory and using kolla to put into its final place, or directly mounted to where the service expects it. The CA bundle is always mounted as the global CA bundle to the final destination (bypassing kolla).
For k8s service a virtual host gets configured using the service name with its corresponding certificate.
Like with other config secrets and config maps, a hash is used to identify if the certificate changed and the deployment pod has to be restarted. For this, the service operators index the named secret fields to be able to watch those if they change. If a change is detected which results in a hash change, the default action is to restart the deployment in order to use the new certificates. There might be services which will handle this differently, but currently there's no option to put new certificates in use without restarting the deployment.
If internal tls is enabled, using spec.tls.podlevel.enabled: true
, routes get configured with spec.tls.termination: reencrypt
and CA added to spec.tls.destinationCACertificate
to be able to validate the certificate of the k8s service.
The configuration of the service is reflecting the k8s service name, not the individual pod name. With this all pods in a scaling resource will share the same service certificate.
For each of the k8s services the openstack-operator requests a certificate for the hostname <service>.<namespace>.svc
and <service>.<namespace>.svc.cluster.local
using the internal and the public CertManager
issuer. For each cert request, CertManger will create a secret holding a tls.crt
, tls.key
and ca.crt
. Those secrets, certificate and the combined CA bundle described above, can be passed into the service operators CRD using the tls section.
It is possible, for a given service, to define additional Subject Alternative Names
that can be added to the CertificateRequest
triggered by the openstack-operator
.
The definition of additional Subject Names is realized through an Annotation
added
to either public or internal services by the service operator.
For example, the OpenStack Image Service (Glance) requires additional Subject Names
DNS entries to ensure that the tls communication works properly when requests are
proxied through replicas of the same glanceAPI resolved by an headless service.
For this reason, the glance-operator
introduces an annotation like the following:
apiVersion: v1
kind: Service
metadata:
annotations:
additionalSubjectNames: '*.glance-default-external-api.openstack.svc,*.glance-default-external-api.openstack.svc.cluster.local'
core.openstack.org/ingress_create: "true"
endpoint: public
labels:
component: glance-api
endpoint: public
glanceAPI: glance-default-external
...
...
The example above shows that a comma separated list of additional Subject Names
can be added as the value of the additionalSubjectNames
key. Such key is
common to all the operators, and it's defined in the tls package of
lib-common/common
module.
When the annotation described above is present, the openstack-operator
is able
to process its content, and it produces a CertificateRequest
where additional
Hostnames
are added (as dnsNames
) to the certificate generation process.
Here's an example of the resulting Certificate
CR processed by the openstack-operator
via the EnsureEndpointConfig
function:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: glance-default-public-svc
namespace: openstack
spec:
dnsNames:
- glance-default-public.openstack.svc
- glance-default-public.openstack.svc.cluster.local
- '*.glance-default-external-api.openstack.svc'
- '*.glance-default-external-api.openstack.svc.cluster.local'
...
...
TBD when e.g. ovn got added
The kuttl test scenarios aim to cover the functionality of TLS within a cluster environment. General guidelines for writing a kuttl test for an operator are available in this section.
The tests for general TLS functions are implemented in the openstack-operator. Currently, the following scenarios are implemented:
- Custom Internal Issuer
- Deploy with the default internal issuer, then switch to a custom internal issuer.
- Deploy with the custom internal issuer, then switch to a default internal issuer.
- Custom Ingress Issuer
- Deploy with the default ingress issuer, then switch to a custom ingress issuer.
- Deploy with the custom ingress issuer, then switch to a default ingress issuer.
- Service Certificate Rotation
- After deployment, certificate secrets are deleted, triggering service restarts. The test checks whether new secrets are mounted in service pods and if route configurations use these new secrets.
- Custom CA cert added to a bundle
- Test whether a custom CA certificate provided via the
caBundleSecretName
is added to thecombined-ca-bundle
.
- Test whether a custom CA certificate provided via the
- Customize cert and CA cert duration parameters
- New durations are set to
500h0m0s
for the service certificates and1000h0m0s
for the CA certificates.
- New durations are set to
Note: To triggert certificate recreation, secrets should not be deleted, this method was used for testing purposes only. Learn more about triggering renewal in the cert-manager ctml documentation.
Each service operator implements its own kuttl tests, which can vary based on the specific operator and the type of TLS service each uses. Generally, the TLS kuttl tests cover the successful creation of volumes and volume mounts that hold the combined-ca-bundle
, public, and internal certificates, ensuring the correct setup if the HTTPS scheme is used, etc. To facilitate kuttl testing, hardcoded certificate secrets are used, meaning kuttl does not require cert-manager at test runtime.