Skip to content

Commit 4da9a14

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 62df400 commit 4da9a14

File tree

4 files changed

+109
-1
lines changed

4 files changed

+109
-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: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,16 @@ func (routeStrategy) NamespaceScoped() bool {
5252
func (s routeStrategy) PrepareForCreate(ctx apirequest.Context, obj runtime.Object) {
5353
route := obj.(*api.Route)
5454
route.Status = api.RouteStatus{}
55+
56+
stripEmptyDestinationCACertificate(route)
5557
}
5658

5759
func (s routeStrategy) PrepareForUpdate(ctx apirequest.Context, obj, old runtime.Object) {
5860
route := obj.(*api.Route)
5961
oldRoute := old.(*api.Route)
60-
route.Status = oldRoute.Status
6162

63+
route.Status = oldRoute.Status
64+
stripEmptyDestinationCACertificate(route)
6265
// Ignore attempts to clear the spec Host
6366
// Prevents "immutable field" errors when applying the same route definition used to create
6467
if len(route.Spec.Host) == 0 {
@@ -218,6 +221,56 @@ func (routeStatusStrategy) ValidateUpdate(ctx apirequest.Context, obj, old runti
218221
return validation.ValidateRouteStatusUpdate(obj.(*api.Route), old.(*api.Route))
219222
}
220223

224+
const emptyDestinationCertificate = `-----BEGIN COMMENT-----
225+
This is an empty PEM file created to provide backwards compatibility
226+
for reencrypt routes that have no destinationCACertificate. This
227+
content will only appear for routes accessed via /oapi/v1/routes.
228+
-----END COMMENT-----
229+
`
230+
231+
// stripEmptyDestinationCACertificate removes the empty destinationCACertificate if it matches
232+
// the current route destination CA certificate.
233+
func stripEmptyDestinationCACertificate(route *api.Route) {
234+
tls := route.Spec.TLS
235+
if tls == nil || tls.Termination != api.TLSTerminationReencrypt {
236+
return
237+
}
238+
if tls.DestinationCACertificate == emptyDestinationCertificate {
239+
tls.DestinationCACertificate = ""
240+
}
241+
}
242+
243+
// DecorateLegacyRouteWithEmptyDestinationCACertificates is used for /oapi/v1 route endpoints
244+
// to prevent legacy clients from seeing an empty destination CA certificate for reencrypt routes,
245+
// which the 'route.openshift.io/v1' endpoint allows. These values are injected in REST responses
246+
// and stripped in PrepareForCreate and PrepareForUpdate.
247+
func DecorateLegacyRouteWithEmptyDestinationCACertificates(obj runtime.Object) error {
248+
switch t := obj.(type) {
249+
case *api.Route:
250+
tls := t.Spec.TLS
251+
if tls == nil || tls.Termination != api.TLSTerminationReencrypt {
252+
return nil
253+
}
254+
if len(tls.DestinationCACertificate) == 0 {
255+
tls.DestinationCACertificate = emptyDestinationCertificate
256+
}
257+
return nil
258+
case *api.RouteList:
259+
for i := range t.Items {
260+
tls := t.Items[i].Spec.TLS
261+
if tls == nil || tls.Termination != api.TLSTerminationReencrypt {
262+
continue
263+
}
264+
if len(tls.DestinationCACertificate) == 0 {
265+
tls.DestinationCACertificate = emptyDestinationCertificate
266+
}
267+
}
268+
return nil
269+
default:
270+
return fmt.Errorf("unknown type passed to %T", obj)
271+
}
272+
}
273+
221274
// GetAttrs returns labels and fields of a given object for filtering purposes
222275
func GetAttrs(obj runtime.Object) (objLabels labels.Set, objFields fields.Set, err error) {
223276
route, ok := obj.(*api.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 *api.Route
72+
}{
73+
{
74+
route: &api.Route{
75+
ObjectMeta: metav1.ObjectMeta{
76+
Namespace: "foo",
77+
Name: "myroute",
78+
UID: types.UID("abc"),
79+
ResourceVersion: "1",
80+
},
81+
Spec: api.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.(*api.Route)); err != nil {
90+
t.Errorf("%d: unexpected error: %v", i, err)
91+
continue
92+
}
93+
routeStrategy{}.PrepareForCreate(nil, copied.(*api.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.(*api.Route)); err != nil {
99+
t.Errorf("%d: unexpected error: %v", i, err)
100+
continue
101+
}
102+
routeStrategy{}.PrepareForUpdate(nil, copied.(*api.Route), &api.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)