Skip to content

Commit

Permalink
[tlse] tls for NeutronAPI pod configuration
Browse files Browse the repository at this point in the history
Public/Internal service cert secrets and the CA bundle secret
can be passed to configure httpd virtual hosts for tls termination.
The certs get direct mounted to the appropriate place in
etc/pki/tls/certs/%s.crt|key and a CA bundle to
/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem . Job deployments
for bootstrap/cron get the CA bundle added if configured.

Also indexes the named input resources for password, CA bundle,
and endpoint secrets to be able to watch them for any changes and
reconcile if needed.

Depends-On: openstack-k8s-operators/lib-common#428

Jira: OSPRH-2197
  • Loading branch information
stuggi committed Jan 10, 2024
1 parent 9d65ae5 commit b7dd116
Show file tree
Hide file tree
Showing 18 changed files with 581 additions and 67 deletions.
30 changes: 30 additions & 0 deletions api/bases/neutron.openstack.org_neutronapis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2237,6 +2237,36 @@ spec:
description: ServiceUser - optional username used for this service
to register in neutron
type: string
tls:
description: TLS - Parameters related to the TLS
properties:
api:
description: API tls type which encapsulates for API services
properties:
internal:
description: Internal GenericService - holds the secret for
the internal endpoint
properties:
secretName:
description: SecretName - holding the cert, key for the
service
type: string
type: object
public:
description: Public GenericService - holds the secret for
the public endpoint
properties:
secretName:
description: SecretName - holding the cert, key for the
service
type: string
type: object
type: object
caBundleSecretName:
description: CaBundleSecretName - holding the CA certs in a pre-created
bundle file
type: string
type: object
required:
- containerImage
- databaseInstance
Expand Down
2 changes: 1 addition & 1 deletion api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/openstack-k8s-operators/neutron-operator/api
go 1.19

require (
github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20240106101723-5f7aa263457f
github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20240110131857-e70e1dec4d14
github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20240106101723-5f7aa263457f
k8s.io/api v0.26.12
k8s.io/apimachinery v0.26.12
Expand Down
4 changes: 2 additions & 2 deletions api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20240106101723-5f7aa263457f h1:ifW2n0TkrS1Wa58DK/N+zAKdGH5XQh4Llk/hefsXzN8=
github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20240106101723-5f7aa263457f/go.mod h1:ov4lAbniNUsLqZCBp1RTixpqXc8JlzA5B+yTcCkJXQg=
github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20240110131857-e70e1dec4d14 h1:8batipIElAHscbsVUJz8w/2NOvu+pRi8ixF1XUP6WiQ=
github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20240110131857-e70e1dec4d14/go.mod h1:ov4lAbniNUsLqZCBp1RTixpqXc8JlzA5B+yTcCkJXQg=
github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20240106101723-5f7aa263457f h1:b9fpRkubG+tk6uKGCNz/kuTWYtpUFsm3d/jECF1AmAs=
github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20240106101723-5f7aa263457f/go.mod h1:MwShIB0G7riRDWXS2JQfcdETm+yutb3qpdnxu/yg+Xk=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down
7 changes: 6 additions & 1 deletion api/v1beta1/neutronapi_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package v1beta1
import (
"github.com/openstack-k8s-operators/lib-common/modules/common/condition"
"github.com/openstack-k8s-operators/lib-common/modules/common/service"
"github.com/openstack-k8s-operators/lib-common/modules/common/tls"
"github.com/openstack-k8s-operators/lib-common/modules/common/util"
"github.com/openstack-k8s-operators/lib-common/modules/storage"

Expand Down Expand Up @@ -64,7 +65,6 @@ type NeutronAPISpec struct {
// Needed to request a transportURL that is created and used in Neutron
RabbitMqClusterName string `json:"rabbitMqClusterName"`


// +kubebuilder:validation:Required
// +kubebuilder:default=memcached
// Memcached instance name.
Expand Down Expand Up @@ -132,6 +132,11 @@ type NeutronAPISpec struct {
// +kubebuilder:validation:Optional
// Override, provides the ability to override the generated manifest of several child resources.
Override APIOverrideSpec `json:"override,omitempty"`

// +kubebuilder:validation:Optional
// +operator-sdk:csv:customresourcedefinitions:type=spec
// TLS - Parameters related to the TLS
TLS tls.API `json:"tls,omitempty"`
}

// APIOverrideSpec to override the generated manifest of several child resources.
Expand Down
1 change: 1 addition & 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.

30 changes: 30 additions & 0 deletions config/crd/bases/neutron.openstack.org_neutronapis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2237,6 +2237,36 @@ spec:
description: ServiceUser - optional username used for this service
to register in neutron
type: string
tls:
description: TLS - Parameters related to the TLS
properties:
api:
description: API tls type which encapsulates for API services
properties:
internal:
description: Internal GenericService - holds the secret for
the internal endpoint
properties:
secretName:
description: SecretName - holding the cert, key for the
service
type: string
type: object
public:
description: Public GenericService - holds the secret for
the public endpoint
properties:
secretName:
description: SecretName - holding the cert, key for the
service
type: string
type: object
type: object
caBundleSecretName:
description: CaBundleSecretName - holding the CA certs in a pre-created
bundle file
type: string
type: object
required:
- containerImage
- databaseInstance
Expand Down
8 changes: 8 additions & 0 deletions config/samples/neutron_v1beta1_neutronapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ spec:
service: false
preserveJobs: false
secret: neutron-secret
#tls:
# api:
# disabled: false
# internal:
# secretName: cert-internal-svc
# public:
# secretName: cert-public-svc
# caBundleSecretName: combined-ca-bundle
178 changes: 176 additions & 2 deletions controllers/neutronapi_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,18 @@ import (
"time"

"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"

Expand All @@ -49,6 +53,7 @@ import (
common_rbac "github.com/openstack-k8s-operators/lib-common/modules/common/rbac"
"github.com/openstack-k8s-operators/lib-common/modules/common/secret"
"github.com/openstack-k8s-operators/lib-common/modules/common/service"
"github.com/openstack-k8s-operators/lib-common/modules/common/tls"
"github.com/openstack-k8s-operators/lib-common/modules/common/util"
mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
neutronv1beta1 "github.com/openstack-k8s-operators/neutron-operator/api/v1beta1"
Expand Down Expand Up @@ -200,8 +205,74 @@ func (r *NeutronAPIReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return r.reconcileNormal(ctx, instance, helper)
}

// fields to index to reconcile when change
const (
passwordSecretField = ".spec.secret"
caBundleSecretNameField = ".spec.tls.caBundleSecretName"
tlsAPIInternalField = ".spec.tls.api.internal.secretName"
tlsAPIPublicField = ".spec.tls.api.public.secretName"
)

var (
allWatchFields = []string{
passwordSecretField,
caBundleSecretNameField,
tlsAPIInternalField,
tlsAPIPublicField,
}
)

// SetupWithManager -
func (r *NeutronAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {

// index passwordSecretField
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &neutronv1beta1.NeutronAPI{}, passwordSecretField, func(rawObj client.Object) []string {
// Extract the secret name from the spec, if one is provided
cr := rawObj.(*neutronv1beta1.NeutronAPI)
if cr.Spec.Secret == "" {
return nil
}
return []string{cr.Spec.Secret}
}); err != nil {
return err
}

// index caBundleSecretNameField
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &neutronv1beta1.NeutronAPI{}, caBundleSecretNameField, func(rawObj client.Object) []string {
// Extract the secret name from the spec, if one is provided
cr := rawObj.(*neutronv1beta1.NeutronAPI)
if cr.Spec.TLS.CaBundleSecretName == "" {
return nil
}
return []string{cr.Spec.TLS.CaBundleSecretName}
}); err != nil {
return err
}

// index tlsAPIInternalField
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &neutronv1beta1.NeutronAPI{}, tlsAPIInternalField, func(rawObj client.Object) []string {
// Extract the secret name from the spec, if one is provided
cr := rawObj.(*neutronv1beta1.NeutronAPI)
if cr.Spec.TLS.API.Internal.SecretName == nil {
return nil
}
return []string{*cr.Spec.TLS.API.Internal.SecretName}
}); err != nil {
return err
}

// index tlsAPIPublicField
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &neutronv1beta1.NeutronAPI{}, tlsAPIPublicField, func(rawObj client.Object) []string {
// Extract the secret name from the spec, if one is provided
cr := rawObj.(*neutronv1beta1.NeutronAPI)
if cr.Spec.TLS.API.Public.SecretName == nil {
return nil
}
return []string{*cr.Spec.TLS.API.Public.SecretName}
}); err != nil {
return err
}

crs := &neutronv1beta1.NeutronAPIList{}
return ctrl.NewControllerManagedBy(mgr).
For(&neutronv1beta1.NeutronAPI{}).
Expand All @@ -218,9 +289,47 @@ func (r *NeutronAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma
Owns(&rbacv1.RoleBinding{}).
Watches(&source.Kind{Type: &ovnclient.OVNDBCluster{}}, handler.EnqueueRequestsFromMapFunc(ovnclient.OVNDBClusterNamespaceMapFunc(crs, mgr.GetClient(), r.GetLogger(ctx)))).
Watches(&source.Kind{Type: &memcachedv1.Memcached{}}, handler.EnqueueRequestsFromMapFunc(r.memcachedNamespaceMapFunc(ctx, crs))).
Watches(
&source.Kind{Type: &corev1.Secret{}},
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
).
Complete(r)
}

func (r *NeutronAPIReconciler) findObjectsForSrc(src client.Object) []reconcile.Request {
requests := []reconcile.Request{}

l := log.FromContext(context.Background()).WithName("Controllers").WithName("NeutronAPI")

for _, field := range allWatchFields {
crList := &neutronv1beta1.NeutronAPIList{}
listOps := &client.ListOptions{
FieldSelector: fields.OneTermEqualSelector(field, src.GetName()),
Namespace: src.GetNamespace(),
}
err := r.List(context.TODO(), crList, listOps)
if err != nil {
return []reconcile.Request{}
}

for _, item := range crList.Items {
l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace()))

requests = append(requests,
reconcile.Request{
NamespacedName: types.NamespacedName{
Name: item.GetName(),
Namespace: item.GetNamespace(),
},
},
)
}
}

return requests
}

func (r *NeutronAPIReconciler) reconcileDelete(ctx context.Context, instance *neutronv1beta1.NeutronAPI, helper *helper.Helper) (ctrl.Result, error) {
Log := r.GetLogger(ctx)
Log.Info("Reconciling Service delete")
Expand Down Expand Up @@ -364,6 +473,39 @@ func (r *NeutronAPIReconciler) reconcileInit(
return ctrl.Result{}, err
}

//
// TLS input validation
//
// Validate the CA cert secret if provided
if instance.Spec.TLS.CaBundleSecretName != "" {
hash, ctrlResult, err := tls.ValidateCACertSecret(
ctx,
helper.GetClient(),
types.NamespacedName{
Name: instance.Spec.TLS.CaBundleSecretName,
Namespace: instance.Namespace,
},
)
if err != nil {
return ctrlResult, err
} else if (ctrlResult != ctrl.Result{}) {
return ctrlResult, nil
}

if hash != "" {
secretVars[tls.CABundleKey] = env.SetValue(hash)
}
}

// Validate API service certs secrets
certsHash, ctrlResult, err := instance.Spec.TLS.API.ValidateCertSecrets(ctx, helper, instance.Namespace)
if err != nil {
return ctrlResult, err
} else if (ctrlResult != ctrl.Result{}) {
return ctrlResult, nil
}
secretVars[tls.TLSHashName] = env.SetValue(certsHash)

//
// create hash over all the different input resources to identify if any those changed
// and a restart/recreate is required.
Expand Down Expand Up @@ -525,7 +667,12 @@ func (r *NeutronAPIReconciler) reconcileInit(
}
// create service - end

// TODO: TLS, pass in https as protocol, create TLS cert
// if TLS is enabled
if instance.Spec.TLS.API.Enabled(endpointType) {
// set endpoint protocol to https
data.Protocol = ptr.To(service.ProtocolHTTPS)
}

apiEndpoints[string(endpointType)], err = svc.GetAPIEndpoint(
svcOverride.EndpointURL, data.Protocol, data.Path)
if err != nil {
Expand Down Expand Up @@ -840,7 +987,16 @@ func (r *NeutronAPIReconciler) reconcileNormal(ctx context.Context, instance *ne
return ctrlResult, fmt.Errorf("Failed to fetch input hash for Neutron deployment")
}

deplDef := neutronapi.Deployment(instance, inputHash, serviceLabels, serviceAnnotations)
deplDef, err := neutronapi.Deployment(instance, inputHash, serviceLabels, serviceAnnotations)
if err != nil {
instance.Status.Conditions.Set(condition.FalseCondition(
condition.DeploymentReadyCondition,
condition.ErrorReason,
condition.SeverityWarning,
condition.DeploymentReadyErrorMessage,
err.Error()))
return ctrlResult, err
}
depl := deployment.NewDeployment(
deplDef,
time.Duration(5)*time.Second,
Expand Down Expand Up @@ -1327,6 +1483,22 @@ func (r *NeutronAPIReconciler) generateServiceSecrets(
templateParameters["NBConnection"] = nbEndpoint
templateParameters["SBConnection"] = sbEndpoint

// create httpd vhost template parameters
httpdVhostConfig := map[string]interface{}{}
for _, endpt := range []service.Endpoint{service.EndpointInternal, service.EndpointPublic} {
endptConfig := map[string]interface{}{}
endptConfig["ServerName"] = fmt.Sprintf("neutron-%s.%s.svc", endpt.String(), instance.Namespace)
endptConfig["TLS"] = false // default TLS to false, and set it bellow to true if enabled
if instance.Spec.TLS.API.Enabled(endpt) {
endptConfig["TLS"] = true
endptConfig["SSLCertificateFile"] = fmt.Sprintf("/etc/pki/tls/certs/%s.crt", endpt.String())
endptConfig["SSLCertificateKeyFile"] = fmt.Sprintf("/etc/pki/tls/private/%s.key", endpt.String())
}
httpdVhostConfig[endpt.String()] = endptConfig
}

templateParameters["VHosts"] = httpdVhostConfig

secrets := []util.Template{
{
Name: fmt.Sprintf("%s-config", instance.Name),
Expand All @@ -1346,7 +1518,9 @@ func (r *NeutronAPIReconciler) generateServiceSecrets(
AdditionalTemplate: map[string]string{
"httpd.conf": "/neutronapi/httpd/httpd.conf",
"10-neutron-httpd.conf": "/neutronapi/httpd/10-neutron-httpd.conf",
"ssl.conf": "/neutronapi/httpd/ssl.conf",
},
ConfigOptions: templateParameters,
},
}
return secret.EnsureSecrets(ctx, h, instance, secrets, envVars)
Expand Down
Loading

0 comments on commit b7dd116

Please sign in to comment.