Skip to content

Commit a3c81a1

Browse files
Preserve backwards compatibilty for old routes for destination CA
OpenShift 3.6 allows destinationCACertificates on the new route.openshift.io group for reencrypt routes to be empty. To preserve backwards compatibility for the existing route API, set a simple "no-op" PEM into the returned REST response, and strip it if a client round trips it. A v1 client that tries to send an empty destinationCACertificate will be allowed to do so, but will get back a response that includes the empty PEM file.
1 parent 3704f7f commit a3c81a1

File tree

4 files changed

+108
-1
lines changed

4 files changed

+108
-1
lines changed

pkg/cmd/server/origin/legacy.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77

88
buildconfigetcd "github.com/openshift/origin/pkg/build/registry/buildconfig/etcd"
99
deploymentconfigetcd "github.com/openshift/origin/pkg/deploy/registry/deployconfig/etcd"
10+
routeregistry "github.com/openshift/origin/pkg/route/registry/route"
11+
routeetcd "github.com/openshift/origin/pkg/route/registry/route/etcd"
1012
)
1113

1214
var (
@@ -204,6 +206,11 @@ func LegacyStorage(storage map[schema.GroupVersion]map[string]rest.Storage) map[
204206
store := *restStorage.Store
205207
restStorage.DeleteStrategy = orphanByDefault(store.DeleteStrategy)
206208
legacyStorage[resource] = &deploymentconfigetcd.REST{Store: &store}
209+
case "routes":
210+
restStorage := s.(*routeetcd.REST)
211+
store := *restStorage.Store
212+
store.Decorator = routeregistry.DecorateLegacyRouteWithEmptyDestinationCACertificates
213+
legacyStorage[resource] = &routeetcd.REST{Store: &store}
207214

208215
default:
209216
legacyStorage[resource] = s

pkg/route/registry/route/strategy.go

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,15 @@ func (routeStrategy) NamespaceScoped() bool {
5252
func (s routeStrategy) PrepareForCreate(ctx apirequest.Context, obj runtime.Object) {
5353
route := obj.(*routeapi.Route)
5454
route.Status = routeapi.RouteStatus{}
55+
stripEmptyDestinationCACertificate(route)
5556
}
5657

5758
func (s routeStrategy) PrepareForUpdate(ctx apirequest.Context, obj, old runtime.Object) {
5859
route := obj.(*routeapi.Route)
5960
oldRoute := old.(*routeapi.Route)
60-
route.Status = oldRoute.Status
6161

62+
route.Status = oldRoute.Status
63+
stripEmptyDestinationCACertificate(route)
6264
// Ignore attempts to clear the spec Host
6365
// Prevents "immutable field" errors when applying the same route definition used to create
6466
if len(route.Spec.Host) == 0 {
@@ -218,6 +220,56 @@ func (routeStatusStrategy) ValidateUpdate(ctx apirequest.Context, obj, old runti
218220
return validation.ValidateRouteStatusUpdate(obj.(*routeapi.Route), old.(*routeapi.Route))
219221
}
220222

223+
const emptyDestinationCertificate = `-----BEGIN COMMENT-----
224+
This is an empty PEM file created to provide backwards compatibility
225+
for reencrypt routes that have no destinationCACertificate. This
226+
content will only appear for routes accessed via /oapi/v1/routes.
227+
-----END COMMENT-----
228+
`
229+
230+
// stripEmptyDestinationCACertificate removes the empty destinationCACertificate if it matches
231+
// the current route destination CA certificate.
232+
func stripEmptyDestinationCACertificate(route *routeapi.Route) {
233+
tls := route.Spec.TLS
234+
if tls == nil || tls.Termination != routeapi.TLSTerminationReencrypt {
235+
return
236+
}
237+
if tls.DestinationCACertificate == emptyDestinationCertificate {
238+
tls.DestinationCACertificate = ""
239+
}
240+
}
241+
242+
// DecorateLegacyRouteWithEmptyDestinationCACertificates is used for /oapi/v1 route endpoints
243+
// to prevent legacy clients from seeing an empty destination CA certificate for reencrypt routes,
244+
// which the 'route.openshift.io/v1' endpoint allows. These values are injected in REST responses
245+
// and stripped in PrepareForCreate and PrepareForUpdate.
246+
func DecorateLegacyRouteWithEmptyDestinationCACertificates(obj runtime.Object) error {
247+
switch t := obj.(type) {
248+
case *routeapi.Route:
249+
tls := t.Spec.TLS
250+
if tls == nil || tls.Termination != routeapi.TLSTerminationReencrypt {
251+
return nil
252+
}
253+
if len(tls.DestinationCACertificate) == 0 {
254+
tls.DestinationCACertificate = emptyDestinationCertificate
255+
}
256+
return nil
257+
case *routeapi.RouteList:
258+
for i := range t.Items {
259+
tls := t.Items[i].Spec.TLS
260+
if tls == nil || tls.Termination != routeapi.TLSTerminationReencrypt {
261+
continue
262+
}
263+
if len(tls.DestinationCACertificate) == 0 {
264+
tls.DestinationCACertificate = emptyDestinationCertificate
265+
}
266+
}
267+
return nil
268+
default:
269+
return fmt.Errorf("unknown type passed to %T", obj)
270+
}
271+
}
272+
221273
// GetAttrs returns labels and fields of a given object for filtering purposes
222274
func GetAttrs(obj runtime.Object) (objLabels labels.Set, objFields fields.Set, err error) {
223275
route, ok := obj.(*routeapi.Route)

pkg/route/registry/route/strategy_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package route
22

33
import (
4+
"reflect"
45
"testing"
56

67
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -65,6 +66,47 @@ func TestEmptyHostDefaulting(t *testing.T) {
6566
}
6667
}
6768

69+
func TestEmptyDefaultCACertificate(t *testing.T) {
70+
testCases := []struct {
71+
route *routeapi.Route
72+
}{
73+
{
74+
route: &routeapi.Route{
75+
ObjectMeta: metav1.ObjectMeta{
76+
Namespace: "foo",
77+
Name: "myroute",
78+
UID: types.UID("abc"),
79+
ResourceVersion: "1",
80+
},
81+
Spec: routeapi.RouteSpec{
82+
Host: "myhost.com",
83+
},
84+
},
85+
},
86+
}
87+
for i, testCase := range testCases {
88+
copied, _ := kapi.Scheme.Copy(testCase.route)
89+
if err := DecorateLegacyRouteWithEmptyDestinationCACertificates(copied.(*routeapi.Route)); err != nil {
90+
t.Errorf("%d: unexpected error: %v", i, err)
91+
continue
92+
}
93+
routeStrategy{}.PrepareForCreate(nil, copied.(*routeapi.Route))
94+
if !reflect.DeepEqual(testCase.route, copied) {
95+
t.Errorf("%d: unexpected change: %#v", i, copied)
96+
continue
97+
}
98+
if err := DecorateLegacyRouteWithEmptyDestinationCACertificates(copied.(*routeapi.Route)); err != nil {
99+
t.Errorf("%d: unexpected error: %v", i, err)
100+
continue
101+
}
102+
routeStrategy{}.PrepareForUpdate(nil, copied.(*routeapi.Route), &routeapi.Route{})
103+
if !reflect.DeepEqual(testCase.route, copied) {
104+
t.Errorf("%d: unexpected change: %#v", i, copied)
105+
continue
106+
}
107+
}
108+
}
109+
68110
func TestHostWithWildcardPolicies(t *testing.T) {
69111
ctx := apirequest.NewContext()
70112
ctx = apirequest.WithUser(ctx, &user.DefaultInfo{Name: "bob"})

test/cmd/routes.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ os::cmd::expect_success 'oc delete routes foo'
3232
os::cmd::expect_success_and_text 'oc create route edge --service foo --port=8080' 'created'
3333
os::cmd::expect_success_and_text 'oc create route edge --service bar --port=9090' 'created'
3434

35+
# verify that reencrypt routes with no destination CA return the stub PEM block on the old API
36+
project="$(oc project -q)"
37+
os::cmd::expect_success_and_text 'oc create route reencrypt --service baz --port=9090' 'created'
38+
os::cmd::expect_success_and_text 'oc get --raw /oapi/v1/namespaces/${project}/routes/baz' 'This is an empty PEM file'
39+
os::cmd::expect_success_and_not_text 'oc get --raw /apis/route.openshift.io/v1/namespaces/${project}/routes/baz' 'This is an empty PEM file'
40+
3541
os::cmd::expect_success_and_text 'oc set route-backends foo' 'routes/foo'
3642
os::cmd::expect_success_and_text 'oc set route-backends foo' 'Service'
3743
os::cmd::expect_success_and_text 'oc set route-backends foo' '100'

0 commit comments

Comments
 (0)