Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce certificate controller #140

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Gopkg.lock

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
@@ -1,4 +1,9 @@
package controller
// The certificate controller is responsible for:
//
// 1. Managing a CA for minting self-signed certs
// 2. Managing self-signed certificates for any clusteringresses which require them
// 3. Publishing in-use wildcard certificates to `openshift-config-managed`
package certificate

import (
"context"
Expand All @@ -11,52 +16,90 @@ import (
"math/big"
"time"

logf "github.com/openshift/cluster-ingress-operator/pkg/log"

corev1 "k8s.io/api/core/v1"

"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

"k8s.io/client-go/tools/record"

"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
)

const (
// caCertSecretName is the name of the secret that holds the CA certificate
// that the operator will use to create default certificates for
// clusteringresses.
caCertSecretName = "router-ca"

controllerName = "certificate-controller"
)

// routerCASecretName returns the namespaced name for the router CA secret.
func routerCASecretName(namespace string) types.NamespacedName {
var log = logf.Logger.WithName(controllerName)

// CASecretName returns the namespaced name for the router CA secret.
func CASecretName(operatorNamespace string) types.NamespacedName {
return types.NamespacedName{
Namespace: namespace,
Namespace: operatorNamespace,
Name: caCertSecretName,
}
}

// ensureRouterCACertificateSecret ensures a CA certificate secret exists that
// can be used to sign the default certificates for ClusterIngresses.
func (r *reconciler) ensureRouterCACertificateSecret() (*corev1.Secret, error) {
current, err := r.currentRouterCASecret()
func New(mgr manager.Manager, client client.Client, operatorNamespace string, events <-chan event.GenericEvent) (controller.Controller, error) {
reconciler := &reconciler{
client: client,
recorder: mgr.GetRecorder(controllerName),
operatorNamespace: operatorNamespace,
}
c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: reconciler})
if err != nil {
return nil, err
}
if err := c.Watch(&source.Channel{Source: events}, &handler.EnqueueRequestForObject{}); err != nil {
return nil, err
}
return c, nil
}

type reconciler struct {
client client.Client
recorder record.EventRecorder
operatorNamespace string
}

func (r *reconciler) Reconcile(request reconcile.Request) (reconcile.Result, error) {
current, err := r.currentRouterCASecret()
if err != nil {
return reconcile.Result{}, err
}
if current != nil {
return current, nil
return reconcile.Result{}, nil
}
desired, err := desiredRouterCASecret(r.Namespace)
desired, err := desiredRouterCASecret(r.operatorNamespace)
if err != nil {
return nil, err
return reconcile.Result{}, err
}
if err := r.createRouterCASecret(desired); err != nil {
return nil, fmt.Errorf("failed to create router CA secret: %v", err)
return reconcile.Result{}, fmt.Errorf("failed to create CA secret: %v", err)
}
return r.currentRouterCASecret()
r.recorder.Event(desired, "Normal", "CreatedWildcardCACert", "Created a default wildcard CA certificate")
return reconcile.Result{}, nil
}

// currentRouterCASecret returns the current router CA secret.
func (r *reconciler) currentRouterCASecret() (*corev1.Secret, error) {
name := routerCASecretName(r.Namespace)
name := CASecretName(r.operatorNamespace)
secret := &corev1.Secret{}
if err := r.Client.Get(context.TODO(), name, secret); err != nil {
if err := r.client.Get(context.TODO(), name, secret); err != nil {
if errors.IsNotFound(err) {
return nil, nil
}
Expand Down Expand Up @@ -118,7 +161,6 @@ func generateRouterCA() ([]byte, []byte, error) {
})

return certBytes, keyBytes, nil

}

// desiredRouterCASecret returns the desired router CA secret.
Expand All @@ -128,7 +170,7 @@ func desiredRouterCASecret(namespace string) (*corev1.Secret, error) {
return nil, fmt.Errorf("failed to generate certificate: %v", err)
}

name := routerCASecretName(namespace)
name := CASecretName(namespace)
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name.Name,
Expand All @@ -145,7 +187,7 @@ func desiredRouterCASecret(namespace string) (*corev1.Secret, error) {

// createRouterCASecret creates the router CA secret.
func (r *reconciler) createRouterCASecret(secret *corev1.Secret) error {
if err := r.Client.Create(context.TODO(), secret); err != nil {
if err := r.client.Create(context.TODO(), secret); err != nil {
return err
}
log.Info("created secret", "namespace", secret.Namespace, "name", secret.Name)
Expand Down
30 changes: 25 additions & 5 deletions pkg/operator/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ package controller
import (
"context"
"fmt"
"time"

ingressv1alpha1 "github.com/openshift/cluster-ingress-operator/pkg/apis/ingress/v1alpha1"
"github.com/openshift/cluster-ingress-operator/pkg/dns"
logf "github.com/openshift/cluster-ingress-operator/pkg/log"
"github.com/openshift/cluster-ingress-operator/pkg/manifests"
certcontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller/certificate"
"github.com/openshift/cluster-ingress-operator/pkg/util/slice"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/record"

configv1 "github.com/openshift/api/config/v1"

Expand Down Expand Up @@ -45,7 +48,8 @@ var log = logf.Logger.WithName("controller")
// in the manager namespace.
func New(mgr manager.Manager, config Config) (controller.Controller, error) {
reconciler := &reconciler{
Config: config,
Config: config,
recorder: mgr.GetRecorder("operator-controller"),
}
c, err := controller.New("operator-controller", mgr, controller.Options{Reconciler: reconciler})
if err != nil {
Expand All @@ -70,6 +74,8 @@ type Config struct {
// events.
type reconciler struct {
Config

recorder record.EventRecorder
}

// Reconcile expects request to refer to a clusteringress in the operator
Expand All @@ -95,10 +101,24 @@ func (r *reconciler) Reconcile(request reconcile.Request) (reconcile.Result, err
ingress = nil
}

caSecret, err := r.ensureRouterCACertificateSecret()
if err != nil {
errs = append(errs, fmt.Errorf("failed to ensure CA secret: %v", err))
} else {
caSecretName := certcontroller.CASecretName(r.Namespace)
caSecret := &corev1.Secret{}
if err := r.Client.Get(context.TODO(), caSecretName, caSecret); err != nil {
if errors.IsNotFound(err) {
// Most likely the CA cert controller hasn't created it yet, try again
// after a reasonable time.
result.RequeueAfter = 5 * time.Second
Copy link
Contributor

@Miciah Miciah Feb 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought there were problems using RequeueAfter, so we were no longer using it (see #132).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error take precedence over requeueAfter when it comes to the result processing, which makes sense — if there's no error, requeueAfter would be effective. Does that seem okay here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, seems reasonable.

// TODO: This check won't be necessary if all cert stuff is extracted.
if ingress != nil {
r.recorder.Event(ingress, "Warning", "DefaultWildcardCACertMissing", "The default wildcard CA certificate is missing")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like more of a status issue: failing condition is true if the CA is missing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right but I didn't want to expand status with this PR

}
} else {
errs = append(errs, fmt.Errorf("failed to get CA secret %s: %v", caSecretName, err))
}
caSecret = nil
}

if caSecret != nil {
// TODO: This should be in a different reconciler as it's independent of an
// individual ingress. We only really need to trigger this when a
// clusteringress is added or deleted...
Expand Down
21 changes: 19 additions & 2 deletions pkg/operator/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import (
"fmt"

"github.com/openshift/cluster-ingress-operator/pkg/apis"
ingressv1alpha1 "github.com/openshift/cluster-ingress-operator/pkg/apis/ingress/v1alpha1"
"github.com/openshift/cluster-ingress-operator/pkg/dns"
logf "github.com/openshift/cluster-ingress-operator/pkg/log"
"github.com/openshift/cluster-ingress-operator/pkg/manifests"
operatorconfig "github.com/openshift/cluster-ingress-operator/pkg/operator/config"
operatorcontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
certcontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller/certificate"

configv1 "github.com/openshift/api/config/v1"

Expand All @@ -28,6 +30,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
Expand Down Expand Up @@ -68,6 +71,8 @@ type Operator struct {

manager manager.Manager
caches []cache.Cache
// certEvents is used to trigger the certificate controller.
certEvents chan event.GenericEvent
}

// New creates (but does not start) a new operator from configuration.
Expand Down Expand Up @@ -144,9 +149,15 @@ func New(config operatorconfig.Config, dnsManager dns.Manager, kubeConfig *rest.
})
}

certEvents := make(chan event.GenericEvent)
if _, err := certcontroller.New(operatorManager, kubeClient, config.Namespace, certEvents); err != nil {
return nil, fmt.Errorf("failed to create cacert controller: %v", err)
}

return &Operator{
manager: operatorManager,
caches: []cache.Cache{operandCache},
manager: operatorManager,
caches: []cache.Cache{operandCache},
certEvents: certEvents,

// TODO: These are only needed for the default cluster ingress stuff, which
// should be refactored away.
Expand Down Expand Up @@ -180,6 +191,12 @@ func (o *Operator) Start(stop <-chan struct{}) error {
log.Info("cache synced")
}

// Kick off the certificate controller
go func() {
ci := ingressv1alpha1.ClusterIngress{}
o.certEvents <- event.GenericEvent{Meta: ci.GetObjectMeta()}
}()

// Secondary caches are all synced, so start the manager.
go func() {
errChan <- o.manager.Start(stop)
Expand Down