Skip to content
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
6 changes: 6 additions & 0 deletions pkg/backends/backends.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,12 @@ func (b *Backends) ensureBackendService(sp utils.ServicePort, igLinks []string)
}
}

if b.backendConfigEnabled && sp.BackendConfig != nil {
if err := features.EnsureSecurityPolicy(b.cloud, sp, be, beName); err != nil {
return err
}
}

// If previous health check was legacy type, we need to delete it.
if hasLegacyHC {
if err = b.healthChecker.DeleteLegacy(sp.NodePort); err != nil {
Expand Down
13 changes: 11 additions & 2 deletions pkg/backends/features/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package features

import (
"sort"

"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta"

Expand All @@ -27,14 +29,16 @@ import (
const (
// FeatureHTTP2 defines the feature name of HTTP2.
FeatureHTTP2 = "HTTP2"
// FeatureSecurityPolicy defines the feature name of SecurityPolicy.
FeatureSecurityPolicy = "SecurityPolicy"
)

var (
// versionToFeatures stores the mapping from the required API
// version to feature names.
versionToFeatures = map[meta.Version][]string{
meta.VersionAlpha: []string{FeatureHTTP2},
meta.VersionBeta: []string{},
meta.VersionBeta: []string{FeatureSecurityPolicy},
}
)

Expand All @@ -50,6 +54,11 @@ func featuresFromServicePort(sp *utils.ServicePort) []string {
if sp.Protocol == annotations.ProtocolHTTP2 {
features = append(features, FeatureHTTP2)
}
if sp.BackendConfig != nil && sp.BackendConfig.Spec.SecurityPolicy != nil {
features = append(features, FeatureSecurityPolicy)
}
// Keep feature names sorted to be consistent.
sort.Strings(features)
return features
}

Expand Down Expand Up @@ -86,7 +95,7 @@ var (
}
)

// IsLowerVersion reutrns if v1 is a lower version than v2.
// IsLowerVersion returns if v1 is a lower version than v2.
func IsLowerVersion(v1, v2 meta.Version) bool {
return versionMap[v1] < versionMap[v2]
}
64 changes: 64 additions & 0 deletions pkg/backends/features/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta"

"k8s.io/ingress-gce/pkg/annotations"
backendconfigv1beta1 "k8s.io/ingress-gce/pkg/apis/backendconfig/v1beta1"
"k8s.io/ingress-gce/pkg/utils"
)

Expand All @@ -45,6 +46,29 @@ var (
ID: fakeSvcPortID,
Protocol: annotations.ProtocolHTTP2,
}

svcPortWithSecurityPolicy = utils.ServicePort{
ID: fakeSvcPortID,
BackendConfig: &backendconfigv1beta1.BackendConfig{
Spec: backendconfigv1beta1.BackendConfigSpec{
SecurityPolicy: &backendconfigv1beta1.SecurityPolicyConfig{
Name: "policy-test",
},
},
},
}

svcPortWithHTTP2SecurityPolicy = utils.ServicePort{
ID: fakeSvcPortID,
Protocol: annotations.ProtocolHTTP2,
BackendConfig: &backendconfigv1beta1.BackendConfig{
Spec: backendconfigv1beta1.BackendConfigSpec{
SecurityPolicy: &backendconfigv1beta1.SecurityPolicyConfig{
Name: "policy-test",
},
},
},
}
)

func TestFeaturesFromServicePort(t *testing.T) {
Expand All @@ -63,6 +87,16 @@ func TestFeaturesFromServicePort(t *testing.T) {
svcPort: svcPortWithHTTP2,
expectedFeatures: []string{"HTTP2"},
},
{
desc: "SecurityPolicy",
svcPort: svcPortWithSecurityPolicy,
expectedFeatures: []string{"SecurityPolicy"},
},
{
desc: "HTTP2 + SecurityPolicy",
svcPort: svcPortWithHTTP2SecurityPolicy,
expectedFeatures: []string{"HTTP2", "SecurityPolicy"},
},
}

for _, tc := range testCases {
Expand All @@ -89,6 +123,16 @@ func TestVersionFromFeatures(t *testing.T) {
features: []string{FeatureHTTP2},
expectedVersion: meta.VersionAlpha,
},
{
desc: "SecurityPolicy",
features: []string{FeatureSecurityPolicy},
expectedVersion: meta.VersionBeta,
},
{
desc: "HTTP2 + SecurityPolicy",
features: []string{FeatureHTTP2, FeatureSecurityPolicy},
expectedVersion: meta.VersionAlpha,
},
{
desc: "unknown feature",
features: []string{"whatisthis"},
Expand Down Expand Up @@ -124,6 +168,16 @@ func TestVersionFromDescription(t *testing.T) {
backendServiceDesc: `{"kubernetes.io/service-name":"my-service","kubernetes.io/service-port":"my-port","x-features":["HTTP2"]}`,
expectedVersion: meta.VersionAlpha,
},
{
desc: "SecurityPolicy",
backendServiceDesc: `{"kubernetes.io/service-name":"my-service","kubernetes.io/service-port":"my-port","x-features":["SecurityPolicy"]}`,
expectedVersion: meta.VersionBeta,
},
{
desc: "HTTP2 + SecurityPolicy",
backendServiceDesc: `{"kubernetes.io/service-name":"my-service","kubernetes.io/service-port":"my-port","x-features":["HTTP2","SecurityPolicy"]}`,
expectedVersion: meta.VersionAlpha,
},
{
desc: "HTTP2 + unknown",
backendServiceDesc: `{"kubernetes.io/service-name":"my-service","kubernetes.io/service-port":"my-port","x-features":["HTTP2","whatisthis"]}`,
Expand Down Expand Up @@ -154,6 +208,16 @@ func TestVersionFromServicePort(t *testing.T) {
svcPort: svcPortWithHTTP2,
expectedVersion: meta.VersionAlpha,
},
{
desc: "enabled security policy",
svcPort: svcPortWithSecurityPolicy,
expectedVersion: meta.VersionBeta,
},
{
desc: "enabled http2 + security policy",
svcPort: svcPortWithHTTP2SecurityPolicy,
expectedVersion: meta.VersionAlpha,
},
}

for _, tc := range testCases {
Expand Down
67 changes: 67 additions & 0 deletions pkg/backends/features/securitypolicy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
Copyright 2018 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package features

import (
"fmt"

"github.com/golang/glog"

computebeta "google.golang.org/api/compute/v0.beta"

"k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
gcecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud"
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta"

"k8s.io/ingress-gce/pkg/composite"
"k8s.io/ingress-gce/pkg/utils"
)

// EnsureSecurityPolicy ensures the security policy link on backend service.
// TODO(mrhohn): Emit event when attach/detach security policy to backend service.
func EnsureSecurityPolicy(cloud *gce.GCECloud, sp utils.ServicePort, be *composite.BackendService, beName string) error {
if sp.BackendConfig.Spec.SecurityPolicy == nil {
return nil
}

needsUpdate, policyRef := securityPolicyNeedsUpdate(cloud, be.SecurityPolicy, sp.BackendConfig.Spec.SecurityPolicy.Name)
if !needsUpdate {
return nil
}

glog.V(2).Infof("Setting security policy %q for backend service %s (%s:%s)", policyRef, beName, sp.ID.Service.String(), sp.ID.Port.String())
if err := cloud.SetSecurityPolicyForBetaGlobalBackendService(beName, policyRef); err != nil {
return fmt.Errorf("failed to set security policy %q for backend service %s (%s:%s): %v", policyRef, beName, sp.ID.Service.String(), sp.ID.Port.String(), err)
}
return nil
}

// securityPolicyNeedsUpdate checks if security policy needs update and
// returns the desired policy reference.
func securityPolicyNeedsUpdate(cloud *gce.GCECloud, currentLink, desiredName string) (bool, *computebeta.SecurityPolicyReference) {
currentName, _ := utils.KeyName(currentLink)
if currentName == desiredName {
return false, nil
}
var policyRef *computebeta.SecurityPolicyReference
if desiredName != "" {
policyRef = &computebeta.SecurityPolicyReference{
SecurityPolicy: gcecloud.SelfLink(meta.VersionBeta, cloud.ProjectID(), "securityPolicies", meta.GlobalKey(desiredName)),
}
}
return true, policyRef
}
163 changes: 163 additions & 0 deletions pkg/backends/features/securitypolicy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
Copyright 2018 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package features

import (
"context"
"fmt"
"sync"
"testing"

computebeta "google.golang.org/api/compute/v0.beta"

"k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud"
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta"

backendconfigv1beta1 "k8s.io/ingress-gce/pkg/apis/backendconfig/v1beta1"
"k8s.io/ingress-gce/pkg/composite"
"k8s.io/ingress-gce/pkg/utils"
)

func TestEnsureSecurityPolicy(t *testing.T) {
mockSecurityPolcies := make(map[string]*computebeta.SecurityPolicyReference)
setSecurityPolicyLock := sync.Mutex{}
setSecurityPolicyHook := func(_ context.Context, key *meta.Key, ref *computebeta.SecurityPolicyReference, _ *cloud.MockBetaBackendServices) error {
setSecurityPolicyLock.Lock()
mockSecurityPolcies[key.Name] = ref
setSecurityPolicyLock.Unlock()
return nil
}

testCases := []struct {
desc string
currentBackendService *composite.BackendService
desiredConfig *backendconfigv1beta1.BackendConfig
expectSetCall bool
}{
{
desc: "attach-policy",
currentBackendService: &composite.BackendService{},
desiredConfig: &backendconfigv1beta1.BackendConfig{
Spec: backendconfigv1beta1.BackendConfigSpec{
SecurityPolicy: &backendconfigv1beta1.SecurityPolicyConfig{
Name: "policy-1",
},
},
},
expectSetCall: true,
},
{
desc: "update-policy",
currentBackendService: &composite.BackendService{
SecurityPolicy: "https://www.googleapis.com/compute/beta/projects/test-project/global/securityPolicies/policy-2",
},
desiredConfig: &backendconfigv1beta1.BackendConfig{
Spec: backendconfigv1beta1.BackendConfigSpec{
SecurityPolicy: &backendconfigv1beta1.SecurityPolicyConfig{
Name: "policy-1",
},
},
},
expectSetCall: true,
},
{
desc: "remove-policy",
currentBackendService: &composite.BackendService{
SecurityPolicy: "https://www.googleapis.com/compute/beta/projects/test-project/global/securityPolicies/policy-1",
},
desiredConfig: &backendconfigv1beta1.BackendConfig{
Spec: backendconfigv1beta1.BackendConfigSpec{
SecurityPolicy: &backendconfigv1beta1.SecurityPolicyConfig{
Name: "",
},
},
},
expectSetCall: true,
},
{
desc: "same-policy",
currentBackendService: &composite.BackendService{
SecurityPolicy: "https://www.googleapis.com/compute/beta/projects/test-project/global/securityPolicies/policy-1",
},
desiredConfig: &backendconfigv1beta1.BackendConfig{
Spec: backendconfigv1beta1.BackendConfigSpec{
SecurityPolicy: &backendconfigv1beta1.SecurityPolicyConfig{
Name: "policy-1",
},
},
},
},
{
desc: "empty-policy",
currentBackendService: &composite.BackendService{},
desiredConfig: &backendconfigv1beta1.BackendConfig{},
},
{
desc: "no-specified-policy",
currentBackendService: &composite.BackendService{
SecurityPolicy: "https://www.googleapis.com/compute/beta/projects/test-project/global/securityPolicies/policy-1",
},
desiredConfig: &backendconfigv1beta1.BackendConfig{},
expectSetCall: false,
},
}

for i, tc := range testCases {
tc := tc
i := i
t.Run(tc.desc, func(t *testing.T) {
t.Parallel()

fakeGCE := gce.FakeGCECloud(gce.DefaultTestClusterValues())
fakeBeName := fmt.Sprintf("be-name-XXX-%d", i)

(fakeGCE.Compute().(*cloud.MockGCE)).MockBetaBackendServices.SetSecurityPolicyHook = setSecurityPolicyHook

if err := EnsureSecurityPolicy(fakeGCE, utils.ServicePort{BackendConfig: tc.desiredConfig}, tc.currentBackendService, fakeBeName); err != nil {
t.Errorf("EnsureSecurityPolicy()=%v, want nil", err)
}

if tc.expectSetCall {
// Verify whether the desired policy is set.
policyRef, ok := mockSecurityPolcies[fakeBeName]
if !ok {
t.Errorf("policy not set for backend service %s", fakeBeName)
return
}
policyLink := ""
if policyRef != nil {
policyLink = policyRef.SecurityPolicy
}
desiredPolicyName := ""
if tc.desiredConfig != nil && tc.desiredConfig.Spec.SecurityPolicy != nil {
desiredPolicyName = tc.desiredConfig.Spec.SecurityPolicy.Name
}
if utils.EqualResourceIDs(policyLink, desiredPolicyName) {
t.Errorf("got policy %q, want %q", policyLink, desiredPolicyName)
}
} else {
// Verify not set call is made.
policyRef, ok := mockSecurityPolcies[fakeBeName]
if ok {
t.Errorf("unexpected policy %q is set for backend service %s", policyRef, fakeBeName)
}
}
})

}
}