Skip to content

Move to a passthrough route to support mTLS #83

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

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
7 changes: 7 additions & 0 deletions pkg/operator2/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func (c *authOperator) getGeneration() int64 {
func defaultDeployment(
operatorConfig *operatorv1.Authentication,
syncData *configSyncData,
routerSecret *corev1.Secret,
resourceVersions ...string,
) *appsv1.Deployment {
replicas := int32(1) // TODO configurable?
Expand Down Expand Up @@ -63,6 +64,12 @@ func defaultDeployment(
path: serviceCAMount,
keys: []string{serviceCAKey},
},
{
name: routerCertsLocalName,
configmap: false,
path: routerCertsLocalMount,
keys: sets.StringKeySet(routerSecret.Data).List(),
},
} {
v, m := data.split()
volumes = append(volumes, v)
Expand Down
6 changes: 4 additions & 2 deletions pkg/operator2/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
func (c *authOperator) handleOAuthConfig(
operatorConfig *operatorv1.Authentication,
route *routev1.Route,
routerSecret *corev1.Secret,
service *corev1.Service,
consoleConfig *configv1.Console,
infrastructureConfig *configv1.Infrastructure,
Expand Down Expand Up @@ -86,13 +87,14 @@ func (c *authOperator) handleOAuthConfig(
ServingInfo: configv1.ServingInfo{
BindAddress: fmt.Sprintf("0.0.0.0:%d", containerPort),
BindNetwork: "tcp4",
// we have valid serving certs provided by service-ca so that we can use reencrypt routes
// we have valid serving certs provided by service-ca
// this is our main server cert which is used if SNI does not match
CertInfo: configv1.CertInfo{
CertFile: servingCertPathCert,
KeyFile: servingCertPathKey,
},
ClientCA: "", // I think this can be left unset
NamedCertificates: nil,
NamedCertificates: routerSecretToSNI(routerSecret),
MinTLSVersion: crypto.TLSVersionToNameOrDie(crypto.DefaultTLSVersion()),
CipherSuites: crypto.CipherSuitesToNamesOrDie(crypto.DefaultCiphers()),
},
Expand Down
11 changes: 8 additions & 3 deletions pkg/operator2/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ const (

oauthMetadataName = systemConfigPrefix + "metadata"

routerCertsSharedName = "router-certs"
routerCertsLocalName = systemConfigPrefix + routerCertsSharedName
routerCertsLocalMount = systemConfigPathSecrets + "/" + routerCertsLocalName

userConfigPath = "/var/config/user"

servicePort = 443
Expand Down Expand Up @@ -185,11 +189,11 @@ func (c *authOperator) handleSync(operatorConfig *operatorv1.Authentication) err
// BLOCK 1: Metadata
// ==================================

route, err := c.handleRoute()
route, routerSecret, err := c.handleRoute()
if err != nil {
return err
}
resourceVersions = append(resourceVersions, route.GetResourceVersion())
resourceVersions = append(resourceVersions, route.GetResourceVersion(), routerSecret.GetResourceVersion())

// make sure API server sees our metadata as soon as we've got a route with a host
metadata, _, err := resourceapply.ApplyConfigMap(c.configMaps, c.recorder, getMetadataConfigMap(route))
Expand Down Expand Up @@ -241,7 +245,7 @@ func (c *authOperator) handleSync(operatorConfig *operatorv1.Authentication) err
infrastructureConfig := c.handleInfrastructureConfig()
resourceVersions = append(resourceVersions, infrastructureConfig.GetResourceVersion())

oauthConfig, expectedCLIconfig, syncData, err := c.handleOAuthConfig(operatorConfig, route, service, consoleConfig, infrastructureConfig)
oauthConfig, expectedCLIconfig, syncData, err := c.handleOAuthConfig(operatorConfig, route, routerSecret, service, consoleConfig, infrastructureConfig)
if err != nil {
return err
}
Expand Down Expand Up @@ -274,6 +278,7 @@ func (c *authOperator) handleSync(operatorConfig *operatorv1.Authentication) err
expectedDeployment := defaultDeployment(
operatorConfig,
syncData,
routerSecret,
resourceVersions...,
)
// TODO add support for spec.operandSpecs.unsupportedResourcePatches, like:
Expand Down
39 changes: 32 additions & 7 deletions pkg/operator2/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,26 @@ import (

"github.com/golang/glog"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"

configv1 "github.com/openshift/api/config/v1"
routev1 "github.com/openshift/api/route/v1"
)

func (c *authOperator) handleRoute() (*routev1.Route, error) {
func (c *authOperator) handleRoute() (*routev1.Route, *corev1.Secret, error) {
route, err := c.route.Get(targetName, metav1.GetOptions{})
if errors.IsNotFound(err) {
route, err = c.route.Create(defaultRoute())
}
if err != nil {
return nil, err
return nil, nil, err
}

if len(route.Spec.Host) == 0 {
return nil, fmt.Errorf("route has no host: %#v", route)
return nil, nil, fmt.Errorf("route has no host: %#v", route)
}

if err := isValidRoute(route); err != nil {
Expand All @@ -32,14 +34,23 @@ func (c *authOperator) handleRoute() (*routev1.Route, error) {
if err := c.route.Delete(route.Name, opts); err != nil && !errors.IsNotFound(err) {
glog.Infof("failed to delete invalid route: %v", err)
}
return nil, err
return nil, nil, err
}

return route, nil
routerSecret, err := c.secrets.Secrets(targetName).Get(routerCertsLocalName, metav1.GetOptions{})
if err != nil {
return nil, nil, err
}
if len(routerSecret.Data) == 0 {
return nil, nil, fmt.Errorf("router secret is empty: %#v", routerSecret)
}

return route, routerSecret, nil
}

func isValidRoute(route *routev1.Route) error {
// TODO: return all errors at once
// TODO error when fields that should be empty are set

// get the expected settings from the default route
expectedRoute := defaultRoute()
Expand All @@ -64,7 +75,7 @@ func isValidRoute(route *routev1.Route) error {
return fmt.Errorf("route contains wrong TLS termination - '%s' is required: %#v", expTLSTermination, route)
}

if route.Spec.TLS.InsecureEdgeTerminationPolicy != routev1.InsecureEdgeTerminationPolicyRedirect {
if route.Spec.TLS.InsecureEdgeTerminationPolicy != expInsecureEdgeTerminationPolicy {
return fmt.Errorf("route contains wrong insecure termination policy - '%s' is required: %#v", expInsecureEdgeTerminationPolicy, route)
}

Expand All @@ -83,9 +94,23 @@ func defaultRoute() *routev1.Route {
TargetPort: intstr.FromInt(containerPort),
},
TLS: &routev1.TLSConfig{
Termination: routev1.TLSTerminationReencrypt,
Termination: routev1.TLSTerminationPassthrough,
InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect,
},
},
}
}

func routerSecretToSNI(routerSecret *corev1.Secret) []configv1.NamedCertificate {
var out []configv1.NamedCertificate
for key := range routerSecret.Data {
out = append(out, configv1.NamedCertificate{
Names: []string{"*." + key}, // ingress domain is always a wildcard
CertInfo: configv1.CertInfo{ // the cert and key are appended together
CertFile: routerCertsLocalMount + "/" + key,
KeyFile: routerCertsLocalMount + "/" + key,
},
})
}
return out
}
8 changes: 8 additions & 0 deletions pkg/operator2/starter.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,14 @@ func RunOperator(ctx *controllercmd.ControllerContext) error {
return err
}

// add syncing for router certs for all cluster ingresses
if err := resourceSyncer.SyncSecret(
resourcesynccontroller.ResourceLocation{Namespace: targetName, Name: routerCertsLocalName},
resourcesynccontroller.ResourceLocation{Namespace: machineConfigNamespace, Name: routerCertsSharedName},
); err != nil {
return err
}

operator := NewAuthenticationOperator(
*operatorClient,
kubeInformersNamespaced,
Expand Down