Skip to content
This repository has been archived by the owner on Jun 25, 2024. It is now read-only.

Commit

Permalink
Initial commit to generate EDPM certs
Browse files Browse the repository at this point in the history
This PR adds the ability to generate and mount TLS certs in the ansibleEE
pod for each service.

The basic idea is as follows:

Each service has a boolean parameter HasTLSCerts which determines
whether or not certs are expected for this service.  If so, then we
expect that a secret with the name <nodeSet.Name>-<service.Name>-certs will
be created.

This secret contains the TLS certs, keys and cacerts for every node in
the nodeset.  They are referenced as <nodeName>-tls.key, nodeName-tls.crt, nodeName-ca.crt

This secret will be mounted in the openstackAnsibleEE pod at
/var/lib/openstack/certs.  It will be up to the ansible playbook to
move the appropriate certs, keys etc. for each node to the node, and do
any reconfiguration of the service.

More details are below:
1. After the DNS data is created in the openstackdataplanenodeset_controller,
   a call is made to GenerateTLSCerts for each service that has HasTLSCerts: True.
2. GenerateTLSCerts right now only generates a single cert for each node in the
   nodeset using the DNSNames for that node.  As the cert contains the
   DNSNames for all the node's interfaces, it should be usable on all
   interfaces.  This cert is stored in the secret "nodeName-cert".  This
   is the default cert - and it should only be created once - ie. the
   first time this code is run.
3.  Its likely that most services will be able to use this cert.  If a
    service needs a different kind of cert (maybe using ips - for ovn
    for instance, or a different issuer), then code needs to be added to
    the switch statement for that particular service.
4. The secret containing the cert will need to have an ownership
   reference updated so that we can trigger a reconcile when the cert
   changes or is renewed.
  • Loading branch information
vakwetu committed Nov 29, 2023
1 parent f33b749 commit f439f1b
Show file tree
Hide file tree
Showing 16 changed files with 231 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ spec:
items:
type: string
type: array
hasTLSCerts:
type: boolean
label:
maxLength: 53
pattern: '[a-z]([-a-z0-9]*[a-z0-9])'
Expand Down
5 changes: 5 additions & 0 deletions api/v1beta1/openstackdataplaneservice_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ type OpenStackDataPlaneServiceSpec struct {
// OpenStackAnsibleEERunnerImage image to use as the ansibleEE runner image
// +kubebuilder:validation:Optional
OpenStackAnsibleEERunnerImage string `json:"openStackAnsibleEERunnerImage,omitempty" yaml:"openStackAnsibleEERunnerImage,omitempty"`

// HasTLSCerts - Whether the nodes have TLS certs
// +kubebuilder:validation:Optional
// +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"}
HasTLSCerts *bool `json:"hasTLSCerts,omitempty" yaml:"hasTLSCerts,omitempty"`
}

// OpenStackDataPlaneServiceStatus defines the observed state of OpenStackDataPlaneService
Expand Down
5 changes: 5 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ spec:
items:
type: string
type: array
hasTLSCerts:
type: boolean
label:
maxLength: 53
pattern: '[a-z]([-a-z0-9]*[a-z0-9])'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ spec:
displayName: OpenStack Data Plane Service
kind: OpenStackDataPlaneService
name: openstackdataplaneservices.dataplane.openstack.org
specDescriptors:
- description: HasTLSCerts - Whether the nodes have TLS certs
displayName: Has TLSCerts
path: hasTLSCerts
x-descriptors:
- urn:alm:descriptor:com.tectonic.ui:booleanSwitch
statusDescriptors:
- description: Conditions
displayName: Conditions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ metadata:
spec:
label: libvirt
playbook: osp.edpm.libvirt
hasTLSCerts: True
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ spec:
# and ssh-publickey
- nova-migration-ssh-key
playbook: osp.edpm.nova
hasTLSCerts: True
18 changes: 17 additions & 1 deletion controllers/openstackdataplanenodeset_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func (r *OpenStackDataPlaneNodeSetReconciler) Reconcile(ctx context.Context, req
}

// Ensure DNSData Required for Nodes
dnsAddresses, dnsClusterAddresses, ctlplaneSearchDomain, isReady, err := deployment.EnsureDNSData(
dnsAddresses, dnsClusterAddresses, ctlplaneSearchDomain, isReady, allHostnames, allIPs, err := deployment.EnsureDNSData(
ctx, helper,
instance, allIPSets)
if err != nil || !isReady {
Expand All @@ -209,6 +209,22 @@ func (r *OpenStackDataPlaneNodeSetReconciler) Reconcile(ctx context.Context, req
instance.Status.DNSClusterAddresses = dnsClusterAddresses
instance.Status.CtlplaneSearchDomain = ctlplaneSearchDomain

// Issue certs for TLS for services that need them
for _, serviceName := range instance.Spec.Services {
service, err := deployment.GetService(ctx, helper, serviceName)
if err != nil {
return ctrl.Result{}, err
}
if service.Spec.HasTLSCerts != nil && *service.Spec.HasTLSCerts {
result, err = deployment.EnsureTLSCerts(ctx, helper, instance, allHostnames, allIPs, serviceName)
if err != nil {
return ctrl.Result{}, err
} else if (result != ctrl.Result{}) {
return result, nil
}
}
}

ansibleSSHPrivateKeySecret := instance.Spec.NodeTemplate.AnsibleSSHPrivateKeySecret

secretKeys := []string{}
Expand Down
1 change: 1 addition & 0 deletions docs/openstack_dataplaneservice.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ OpenStackDataPlaneServiceSpec defines the desired state of OpenStackDataPlaneSer
| configMaps | ConfigMaps list of ConfigMap names to mount as ExtraMounts for the OpenStackAnsibleEE | []string | false |
| secrets | Secrets list of Secret names to mount as ExtraMounts for the OpenStackAnsibleEE | []string | false |
| openStackAnsibleEERunnerImage | OpenStackAnsibleEERunnerImage image to use as the ansibleEE runner image | string | false |
| hasTLSCerts | HasTLSCerts - Whether the nodes have TLS certs | *bool | false |

[Back to Custom Resources](#custom-resources)

Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ replace golang.org/x/net => golang.org/x/net v0.18.0 //allow-merging
replace github.com/openstack-k8s-operators/dataplane-operator/api => ./api

require (
github.com/cert-manager/cert-manager v1.11.5
github.com/go-logr/logr v1.3.0
github.com/google/uuid v1.4.0
github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.4.0
Expand All @@ -16,6 +17,7 @@ require (
github.com/openstack-k8s-operators/dataplane-operator/api v0.0.0-20230724101130-2d6fe1f4706b
github.com/openstack-k8s-operators/infra-operator/apis v0.3.1-0.20231122104142-3b449040167e
github.com/openstack-k8s-operators/lib-common/modules/ansible v0.3.1-0.20231122111552-6bd6025ade37
github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20231006072650-7fe7fe16bcd1
github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231122111552-6bd6025ade37
github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20231122111552-6bd6025ade37
github.com/openstack-k8s-operators/lib-common/modules/test v0.3.1-0.20231122111552-6bd6025ade37
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cert-manager/cert-manager v1.11.5 h1:K2LurvwIE4hIhODQZnkOW6ljYe3lVMAliS/to+gI05o=
github.com/cert-manager/cert-manager v1.11.5/go.mod h1:zNOyoTEwdn9Rtj5Or2pjBY1Bqwtw4vBElP2fKSP8/g8=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
Expand Down Expand Up @@ -239,6 +241,8 @@ github.com/openstack-k8s-operators/infra-operator/apis v0.3.1-0.20231122104142-3
github.com/openstack-k8s-operators/infra-operator/apis v0.3.1-0.20231122104142-3b449040167e/go.mod h1:FnKU6sravC43Uj0iq2bhZaPMjoPCBhkNlVdiVoGi5/E=
github.com/openstack-k8s-operators/lib-common/modules/ansible v0.3.1-0.20231122111552-6bd6025ade37 h1:DpfHnE6YERlwPX/zd0eaZ59uv/l6+uXThwHTaxdRPQ4=
github.com/openstack-k8s-operators/lib-common/modules/ansible v0.3.1-0.20231122111552-6bd6025ade37/go.mod h1:A9sWNibvjr1a9B/mpy4k6J9xkH11fnn0Dx/X1EZ3On8=
github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20231006072650-7fe7fe16bcd1 h1:sE/qio/WNUEng0VBmefSr46e/cq4R83payEzge/Y48U=
github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20231006072650-7fe7fe16bcd1/go.mod h1:u1pqzqGNLcof95aqhLfU6xHVTD6ZTc5gWy2FE03UrZQ=
github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231122111552-6bd6025ade37 h1:ftwUwA41Y6R8U883Saz7aDg2lqUTh2ewm0qKwLuGLcU=
github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231122111552-6bd6025ade37/go.mod h1:/6//JWNEY68jOMoaoaSI0koL2jzpEKim3m60+jFCbqY=
github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20231122111552-6bd6025ade37 h1:F/sQ5+TzB1dVf4VyeyLDtcyNQDHnIkqZPK9V+cr/f6s=
Expand Down Expand Up @@ -641,6 +645,8 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/controller-runtime v0.14.7 h1:Vrnm2vk9ZFlRkXATHz0W0wXcqNl7kPat8q2JyxVy0Q8=
sigs.k8s.io/controller-runtime v0.14.7/go.mod h1:ErTs3SJCOujNUnTz4AS+uh8hp6DHMo1gj6fFndJT1X8=
sigs.k8s.io/gateway-api v0.6.0 h1:v2FqrN2ROWZLrSnI2o91taHR8Sj3s+Eh3QU7gLNWIqA=
sigs.k8s.io/gateway-api v0.6.0/go.mod h1:EYJT+jlPWTeNskjV0JTki/03WX1cyAnBhwBJfYHpV/0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
Expand Down
4 changes: 4 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import (
baremetalv1 "github.com/openstack-k8s-operators/openstack-baremetal-operator/api/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/client/config"

certmgrv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
certmgrmetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
dataplanev1 "github.com/openstack-k8s-operators/dataplane-operator/api/v1beta1"
dataplanev1beta1 "github.com/openstack-k8s-operators/dataplane-operator/api/v1beta1"
"github.com/openstack-k8s-operators/dataplane-operator/controllers"
Expand All @@ -61,6 +63,8 @@ func init() {
utilruntime.Must(baremetalv1.AddToScheme(scheme))
utilruntime.Must(infranetworkv1.AddToScheme(scheme))
utilruntime.Must(dataplanev1beta1.AddToScheme(scheme))
utilruntime.Must(certmgrv1.AddToScheme(scheme))
utilruntime.Must(certmgrmetav1.AddToScheme(scheme))
//+kubebuilder:scaffold:scheme
}

Expand Down
128 changes: 128 additions & 0 deletions pkg/deployment/cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
Copyright 2023.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package deployment

import (
"context"
"fmt"
"time"

corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

dataplanev1 "github.com/openstack-k8s-operators/dataplane-operator/api/v1beta1"
"github.com/openstack-k8s-operators/lib-common/modules/certmanager"
"github.com/openstack-k8s-operators/lib-common/modules/common/helper"
"github.com/openstack-k8s-operators/lib-common/modules/common/secret"
)

// EnsureTLSCerts generates a secret containing all the certificates for the relevant service
// This secret will be mounted by the ansibleEE pod as an extra mount when the service is deployed.
func EnsureTLSCerts(ctx context.Context, helper *helper.Helper,
instance *dataplanev1.OpenStackDataPlaneNodeSet,
allHostnames map[string][]string,
allIPs map[string][]string,
serviceName string) (ctrl.Result, error) {

certsData := map[string][]byte{}

// for each node in the nodeset, issue all the TLS certs needed based on the
// ips or DNS Names
for nodeName := range instance.Spec.Nodes {
var dnsNames []string
var secretName string
var certName string
var certSecret *corev1.Secret = nil
var err error
var result ctrl.Result

// TODO(alee) decide if we want to use other labels
// For now we just add the hostname so we can select all the certs on one node
labels := map[string]string{
"hostname": nodeName,
}

dnsNames = allHostnames[nodeName]
// ips = allIPs[nodeName]

switch serviceName {
default:
// The default case provides a cert with all the dns names for the host.
// This will probably be sufficient for most services. If a service needs
// a different kind of cert (for example, containing ips, or using a different
// issuer) then add a case for the service in this switch statement

secretName = "cert-" + nodeName
certSecret, _, err = secret.GetSecret(ctx, helper, secretName, instance.Namespace)
if err != nil {
if !k8serrors.IsNotFound(err) {
err = fmt.Errorf("Error retrieving secret %s - %w", secretName, err)
return ctrl.Result{}, err
}

certName = secretName
duration := ptr.To(time.Hour * 24 * 365)
certSecret, result, err = certmanager.EnsureCert(ctx, helper, certmanager.RootCAIssuerInternalLabel,
certName, duration, dnsNames, nil, labels)
if err != nil {
return ctrl.Result{}, err
} else if (result != ctrl.Result{}) {
return result, nil
}
}
}

// TODO(alee) Add an owner reference to the secret so it can be monitored
// We'll do this once stuggi adds a function to do this in libcommon

// To use this cert, add it to the relevant service data
// TODO(alee) We only need the cert and key. The cacert will come from another label
for key, value := range certSecret.Data {
certsData[nodeName+"-"+key] = value
}
}

// create a secret to hold the certs for the service
serviceCertsSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: GetServiceCertsSecretName(instance, serviceName),
Namespace: instance.Namespace,
},
Data: certsData,
}
_, result, err := secret.CreateOrPatchSecret(ctx, helper, instance, serviceCertsSecret)
if err != nil {
err = fmt.Errorf("Error creating certs secret for %s - %w", serviceName, err)
return ctrl.Result{}, err
} else if result != controllerutil.OperationResultNone {
return ctrl.Result{RequeueAfter: time.Second * 5}, nil
}

return ctrl.Result{}, nil
}

// GetServiceCertsSecretName - return name of secret to be mounted in ansibleEE which contains
// all the TLS certs for the relevant service
// The convention we use here is "<nodeset.name>-<service>-certs", so for example,
// openstack-epdm-nova-certs.
func GetServiceCertsSecretName(instance *dataplanev1.OpenStackDataPlaneNodeSet, serviceName string) string {
return fmt.Sprintf("%s-%s-certs", instance.Name, serviceName)
}
3 changes: 3 additions & 0 deletions pkg/deployment/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,7 @@ const (

// ConfigPaths base path for volume mounts in OpenStackAnsibleEE pod
ConfigPaths = "/var/lib/openstack/configs"

// CertPaths base path for cert volume mount in OpenStackAnsibleEE pod
CertPaths = "/var/lib/openstack/certs"
)
32 changes: 31 additions & 1 deletion pkg/deployment/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func Deploy(
// specific mounts.
aeeSpec.ExtraMounts = make([]storage.VolMounts, len(aeeSpecMounts))
copy(aeeSpec.ExtraMounts, aeeSpecMounts)
aeeSpec, err = addServiceExtraMounts(ctx, helper, aeeSpec, foundService)
aeeSpec, err = addServiceExtraMounts(ctx, helper, aeeSpec, foundService, nodeSet)

if err != nil {
return &ctrl.Result{}, err
Expand Down Expand Up @@ -225,6 +225,7 @@ func addServiceExtraMounts(
helper *helper.Helper,
aeeSpec dataplanev1.AnsibleEESpec,
service dataplanev1.OpenStackDataPlaneService,
nodeSet *dataplanev1.OpenStackDataPlaneNodeSet,
) (dataplanev1.AnsibleEESpec, error) {
client := helper.GetClient()
baseMountPath := path.Join(ConfigPaths, service.Name)
Expand Down Expand Up @@ -322,5 +323,34 @@ func addServiceExtraMounts(

aeeSpec.ExtraMounts = append(aeeSpec.ExtraMounts, volMounts)
}

// Add mount for TLS certs
if service.Spec.HasTLSCerts != nil && *service.Spec.HasTLSCerts {
volMounts := storage.VolMounts{}
secretName := GetServiceCertsSecretName(nodeSet, service.Name)
sec := &corev1.Secret{}
err := client.Get(ctx, types.NamespacedName{Name: secretName, Namespace: service.Namespace}, sec)
if err != nil {
return aeeSpec, err
}
volume := corev1.Volume{
Name: secretName,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: secretName,
},
},
}

volumeMount := corev1.VolumeMount{
Name: secretName,
MountPath: path.Join(CertPaths, service.Name),
}

volMounts.Volumes = append(volMounts.Volumes, volume)
volMounts.Mounts = append(volMounts.Mounts, volumeMount)
aeeSpec.ExtraMounts = append(aeeSpec.ExtraMounts, volMounts)

}
return aeeSpec, nil
}
Loading

0 comments on commit f439f1b

Please sign in to comment.