Skip to content

Commit

Permalink
mount certs to oauth
Browse files Browse the repository at this point in the history
  • Loading branch information
awgreene committed Apr 6, 2021
1 parent 6e309a8 commit 47207ac
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 5 deletions.
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/openshift/cluster-authentication-operator/pkg/controllers/configobservation"
"github.com/openshift/cluster-authentication-operator/pkg/controllers/configobservation/console"
"github.com/openshift/cluster-authentication-operator/pkg/controllers/configobservation/customroutersecret"
"github.com/openshift/cluster-authentication-operator/pkg/controllers/configobservation/infrastructure"
"github.com/openshift/cluster-authentication-operator/pkg/controllers/configobservation/oauth"
"github.com/openshift/cluster-authentication-operator/pkg/controllers/configobservation/routersecret"
Expand Down Expand Up @@ -71,6 +72,7 @@ func NewConfigObserver(
oauth.ObserveTokenConfig,
configobserveroauth.ObserveAccessTokenInactivityTimeout,
routersecret.ObserveRouterSecret,
customroutersecret.NewObserveCustomRouterSecret(),
} {
oauthServerObservers = append(oauthServerObservers,
configobserver.WithPrefix(o, configobservation.OAuthServerConfigPrefix))
Expand All @@ -82,6 +84,7 @@ func NewConfigObserver(
configobservation.Listers{
ConfigMapLister: kubeInformersForNamespaces.ConfigMapLister(),
SecretsLister: kubeInformersForNamespaces.SecretLister(),
IngressLister: configInformer.Config().V1().Ingresses().Lister(),

APIServerLister_: configInformer.Config().V1().APIServers().Lister(),
ConsoleLister: configInformer.Config().V1().Consoles().Lister(),
Expand Down
@@ -0,0 +1,135 @@
package customroutersecret

import (
configv1 "github.com/openshift/api/config/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"

"github.com/openshift/library-go/pkg/operator/configobserver"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/operator/resourcesynccontroller"
corev1 "k8s.io/api/core/v1"

"github.com/openshift/cluster-authentication-operator/pkg/controllers/configobservation"
)

func NewObserveCustomRouterSecret() func(configobserver.Listers, events.Recorder, map[string]interface{}) (map[string]interface{}, []error) {
return (&customRouterObserver{
configPaths: [][]string{{"servingInfo", "componentRoutes"}},
destSecret: "v4-0-config-system-custom-router-certs",
componentRoute: types.NamespacedName{
Namespace: "openshift-authentication",
Name: "oauth",
},
resourceType: corev1.Secret{},
}).observe
}

type customRouterObserver struct {
configPaths [][]string
destSecret string
componentRoute types.NamespacedName
resourceType interface{}
}

// extractPreviouslyObservedConfig extracts the previously observed config from the existing config.
func extractPreviouslyObservedConfig(existing map[string]interface{}, paths ...[]string) (map[string]interface{}, []error) {
var errs []error
previous := map[string]interface{}{}
for _, fields := range paths {
value, found, err := unstructured.NestedFieldCopy(existing, fields...)
if !found {
continue
}
if err != nil {
errs = append(errs, err)
}
err = unstructured.SetNestedField(previous, value, fields...)
if err != nil {
errs = append(errs, err)
}
}
return previous, errs
}

func (c *customRouterObserver) observe(genericlisters configobserver.Listers, recorder events.Recorder, existingConfig map[string]interface{}) (ret map[string]interface{}, _ []error) {
listers := genericlisters.(configobservation.Listers)
errs := []error{}

// pick the correct resource sync function
resourceSync := listers.ResourceSyncer().SyncSecret
if _, ok := c.resourceType.(corev1.ConfigMap); ok {
resourceSync = listers.ResourceSyncer().SyncConfigMap
}

previouslyObservedConfig, errs := extractPreviouslyObservedConfig(existingConfig, c.configPaths...)

ingress, err := listers.IngressLister.Get("cluster")
// if something went wrong, keep the previously observed config and resources
if err != nil {
return previouslyObservedConfig, append(errs, err)
}

observedComponentRoutes, observedResources, err := c.componentRouteSecret(listers, ingress)
if err != nil {
return previouslyObservedConfig, append(errs, err)
}

observedConfig := map[string]interface{}{}
if err := unstructured.SetNestedField(observedConfig, observedComponentRoutes, c.configPaths[0]...); err != nil {
return previouslyObservedConfig, append(errs, err)
}

errs = append(errs, syncObservedResources(resourceSync, observedResources)...)
return observedConfig, errs
}

// resourceSyncFunc syncs a resource from the source location to the destination location.
type resourceSyncFunc func(destination, source resourcesynccontroller.ResourceLocation) error

// syncActionRules rules define source resource names indexed by destination resource names.
// Empty value means to delete the destination.
type syncActionRules map[string]string

// syncObservedResources copies or deletes resources, sources in GlobalUserSpecifiedConfigNamespace and destinations in OperatorNamespace namespace.
// Errors are collected, i.e. it's not failing on first error.
func syncObservedResources(syncResource resourceSyncFunc, syncRules syncActionRules) []error {
var errs []error
for to, from := range syncRules {
var source resourcesynccontroller.ResourceLocation
if len(from) > 0 {
source = resourcesynccontroller.ResourceLocation{Namespace: "openshift-config", Name: from}
}
// if 'from' is empty, then it means we want to delete
destination := resourcesynccontroller.ResourceLocation{Namespace: "openshift-authentication", Name: to}
if err := syncResource(destination, source); err != nil {
errs = append(errs, err)
}
}
return errs
}

func (c *customRouterObserver) componentRouteSecret(lister configobservation.Listers, ingress *configv1.Ingress) ([]interface{}, syncActionRules, error) {
componentRoutes := []interface{}{}

rules := syncActionRules{}

// make sure the output slice of named certs is sorted by domain so that the generated config is deterministic
for _, componentRoute := range ingress.Spec.ComponentRoutes {
if componentRoute.Name == c.componentRoute.Name &&
componentRoute.Namespace == c.componentRoute.Namespace {
if _, err := lister.SecretsLister.Secrets("openshift-config").Get(componentRoute.ServingCertKeyPairSecret.Name); err != nil ||
componentRoute.ServingCertKeyPairSecret.Name == "" {
rules[c.destSecret] = ""
} else {
componentRoutes = append(componentRoutes, map[string]interface{}{
"secret": interface{}(componentRoute.ServingCertKeyPairSecret.Name),
"hostname": interface{}(string(componentRoute.Hostname)),
})
rules[c.destSecret] = componentRoute.ServingCertKeyPairSecret.Name
}
return componentRoutes, rules, nil
}
}
return nil, nil, nil
}
1 change: 1 addition & 0 deletions pkg/controllers/configobservation/interfaces.go
Expand Up @@ -23,6 +23,7 @@ type Listers struct {
ConsoleLister configlistersv1.ConsoleLister
InfrastructureLister configlistersv1.InfrastructureLister
OAuthLister_ configlistersv1.OAuthLister
IngressLister configlistersv1.IngressLister

ResourceSync resourcesynccontroller.ResourceSyncer
PreRunCachesSynced []cache.InformerSynced
Expand Down
Expand Up @@ -3,6 +3,7 @@ package routersecret
import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/sets"

Expand All @@ -26,11 +27,26 @@ func ObserveRouterSecret(genericlisters configobserver.Listers, recorder events.
return existingConfig, append(errs, err)
}

observedNamedCertificates, err := routerSecretToSNI(routerSecret)
observedNamedCertificates, err := routerSecretToSNI(routerSecret, "/var/config/system/secrets/v4-0-config-system-router-certs/", "/var/config/system/secrets/v4-0-config-system-router-certs/")
if err != nil {
return existingConfig, append(errs, err)
}

// attempt to get custom serving certs
routerSecret, err = listers.SecretsLister.Secrets("openshift-authentication").Get("v4-0-config-system-custom-router-certs")
if err != nil && !errors.IsNotFound(err) {
return existingConfig, append(errs, err)
}

// If no error occured, add optional custom serving certs
if err == nil {
customNamedCertificates, err := routerSecretToSNI(routerSecret, "/var/config/system/secrets/v4-0-config-system-custom-router-certs/", "/var/config/system/secrets/v4-0-config-system-custom-router-certs/")
if err != nil {
return existingConfig, append(errs, err)
}
observedNamedCertificates = append(observedNamedCertificates, customNamedCertificates...)
}

observedConfig := map[string]interface{}{}
if err := unstructured.SetNestedSlice(
observedConfig,
Expand All @@ -53,16 +69,15 @@ func ObserveRouterSecret(genericlisters configobserver.Listers, recorder events.
return observedConfig, errs
}

func routerSecretToSNI(routerSecret *corev1.Secret) ([]interface{}, error) {
func routerSecretToSNI(routerSecret *corev1.Secret, certFile string, keyFile string) ([]interface{}, error) {
certs := []interface{}{}
// make sure the output slice of named certs is sorted by domain so that the generated config is deterministic
for _, domain := range sets.StringKeySet(routerSecret.Data).List() {
certs = append(certs, map[string]interface{}{
"names": []interface{}{"*." + domain}, // ingress domain is always a wildcard
"certFile": interface{}("/var/config/system/secrets/v4-0-config-system-router-certs/" + domain),
"keyFile": interface{}("/var/config/system/secrets/v4-0-config-system-router-certs/" + domain),
"certFile": interface{}(certFile + domain),
"keyFile": interface{}(keyFile + domain),
})
}

return certs, nil
}
37 changes: 37 additions & 0 deletions pkg/controllers/deployment/default_deployment.go
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/ghodss/yaml"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/klog/v2"

Expand Down Expand Up @@ -84,9 +85,45 @@ func getOAuthServerDeployment(
templateSpec.Volumes = append(templateSpec.Volumes, v...)
container.VolumeMounts = append(container.VolumeMounts, m...)

if isComponentRoutePresent(&operatorConfig.Spec.ObservedConfig) {
templateSpec.Volumes = append(templateSpec.Volumes, corev1.Volume{
Name: "v4-0-config-system-custom-router-certs",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: "v4-0-config-system-custom-router-certs",
},
},
})
container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
Name: "v4-0-config-system-custom-router-certs",
ReadOnly: true,
MountPath: "/var/config/system/secrets/v4-0-config-system-custom-router-certs",
})
}

return deployment, nil
}

func isComponentRoutePresent(operatorConfig *runtime.RawExtension) bool {
var configDeserialized map[string]interface{}
oauthServerObservedConfig, err := common.UnstructuredConfigFrom(operatorConfig.Raw, configobservation.OAuthServerConfigPrefix)
if err != nil {
return false
}

if err := yaml.Unmarshal(oauthServerObservedConfig, &configDeserialized); err != nil {
return false
}

componentRoutePath := []string{"servingInfo", "componentRoutes"}
currentNamedCertificates, _, err := unstructured.NestedFieldCopy(configDeserialized, componentRoutePath...)
if err != nil {
return false
}

return len(currentNamedCertificates.([]interface{})) > 0
}

func getSyncDataFromOperatorConfig(operatorConfig *runtime.RawExtension) (*datasync.ConfigSyncData, error) {
var configDeserialized map[string]interface{}
oauthServerObservedConfig, err := common.UnstructuredConfigFrom(operatorConfig.Raw, configobservation.OAuthServerConfigPrefix)
Expand Down

0 comments on commit 47207ac

Please sign in to comment.