Skip to content

Commit

Permalink
Merge pull request #291 from MrHohn/cloud-armor-support
Browse files Browse the repository at this point in the history
Add support for security policy
  • Loading branch information
bowei authored Jun 19, 2018
2 parents 0a8cdd4 + d40ea79 commit 8821e6c
Show file tree
Hide file tree
Showing 5 changed files with 311 additions and 2 deletions.
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)
}
}
})

}
}

0 comments on commit 8821e6c

Please sign in to comment.