Skip to content

Commit

Permalink
Samples on how to try Guard directly on any Kubernetes system (#81)
Browse files Browse the repository at this point in the history
* kube

* kube

* kube
  • Loading branch information
davidhadas authored Oct 20, 2022
1 parent c88fed5 commit 4459d49
Show file tree
Hide file tree
Showing 13 changed files with 575 additions and 21 deletions.
7 changes: 7 additions & 0 deletions KUBERNETES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Guard on Vanilla Kubernetes

Please refer to [An Opinionated Kubernetes](https://davidhadas.wordpress.com/2022/08/29/knative-an-opinionated-kubernetes/) to learn why Knative should be your preferred path for deploying web services over Kubernetes rather than deploying directly on Kubernetes.

For direct deployment on Kubernetes, Guard needs to be deployed in the service pods as a reverse proxy sidecar and the pod needs to be configured to pass ingress requests via the guard sidecar.

See [documentation and a sidecar image](./cmd/guard-rproxy/README.md).
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@

<h1><img src="img/guard.png" width="80"/> Security-Guard</h1>

This readme file focus on assisting Knative users to secure their deployed services. When working with vanilla Kubernetes, [you may deploy Guard by adding a sidecar](KUBERNETES.md).

## Why Do We Need Security-Guard?

User containers deployed by Knative may include vulnerabilities, may be misconfigured and may include malicious code. The source of such vulnerabilities, misconfigurations or malicious code may be the DevOps team, a dependency or a hacker that has successfully penetrated any of the support systems (image repository, ci/cd, Knative, Kube, the DevOps team development systems etc. etc. etc.) or one of the services used by the user container. Any such security issue may enable an attacker to use the user container for other purposes than its original intention (e.g. steal data, attack others, spread, contact a C&C, Crypto mining, etc.)
User containers deployed on Kubernetes may include vulnerabilities, may be misconfigured and may include malicious code. The source of such vulnerabilities, misconfigurations or malicious code may be the DevOps team, a dependency or a hacker that has successfully penetrated any of the support systems (image repository, ci/cd, Knative, Kube, the DevOps team development systems etc. etc. etc.) or one of the backend services used by the user container. Any such security issue may enable an attacker to use the user container for other purposes than its original intention (e.g. steal data, attack others, spread, contact a C&C, Crypto mining, etc.)

Users of Knative require the means to block (and/or get an alert about) an attempt to exploit a vulnerability or misconfiguration, embedded in a user container. Also, users of Knative require the means to establish situational awareness about container running potentially malicious code and be offered ways to respond once they discover that the user containers are being exploited by attackers.
Users of Knative, same as any Kubernetes user, require the means to block (and/or get an alert about) an attempt to exploit a vulnerability or misconfiguration, embedded in a user container. Also, users require the means to establish situational awareness about container running potentially malicious code and be offered ways to respond once they discover that the user containers are being exploited by attackers.

## How Does Security-Guard Help Secure Knative Services

Expand Down Expand Up @@ -55,11 +57,6 @@ See [Guard Architecture](ARCHITECTURE.md) to learn about how Guard process and l

Once loaded, it monitors the proxied requests and responses and the pod network.

Note that [guard-gate](pkg/guard-gate) can also be used for more generic Kubernetes use cases by loading:

- As a standalone reverse proxy, see for example: [guard-rproxy](https://github.com/IBM/workload-security-guard/tree/main/cmd/guard-rproxy)
- As an extension to any go proxy, for example by using: [rtplugs](https://github.com/IBM/go-security-plugs/tree/main/rtplugs).

## Guardian

[guard-gate](pkg/guard-gate) uses **Guardian** - a set of micro-rules that define the expected behavior of the service.
Expand Down
56 changes: 56 additions & 0 deletions cmd/guard-rproxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Guard Reverse Proxy Sidecar

guard-rproxy is a reverse proxy embedded with a guard-gate and packed as a container image. The container image can than be used:

1. As a sidecar while deploying a Kubernetes microservice. This is the recommended mode of operation and offers both client request monitoring and control and microservice pod monitoring and control.

1. As a standalone exposed Pod in from of a protected unexposed microservice. This mode is simple to try out. It offers client request monitoring and control but does not offer microservice pod monitoring and control.

## Installing Security-Guard

Security-Guard includes automation for auto-learning a per service Guardian.
Auto-learning requires you to deploy a `guard-service` on your kubernetes cluster.
`guard-service` should be installed in any namespace where you deploy knative services that require Security-Guard protection.

### Install from source

1. Clone the Security-Guard repository using `git clone git@github.com:knative-sandbox/security-guard.git`
1. Do `cd security-guard`
1. Run `ko apply -Rf ./config/resources/`
1. Run `ko apply -Rf ./config/deploy/guard-service.yaml`

### Install from released images and yamls

Use released images to update your system to enable Security-Guard:

1. Add the necessary Security-Guard resources to your cluster using:

kubectl apply -f https://raw.githubusercontent.com/knative-sandbox/security-guard/release-0.1/config/resources/gateAccount.yaml
kubectl apply -f https://raw.githubusercontent.com/knative-sandbox/security-guard/release-0.1/config/resources/serviceAccount.yaml
kubectl apply -f https://raw.githubusercontent.com/knative-sandbox/security-guard/release-0.1/config/resources/guardiansCrd.yaml

1. Deploy `guard-service` on your system to enable automated learning of micro-rules. In the current version, it is recommended to deploy `guard-service` in any namespace where knative services are deployed.

An easy way to do that is using:

kubectl apply -f https://github.com/knative-sandbox/security-guard/releases/download/v0.1.2/guard-service.yaml

## Deploying a pod with a Security-Guard sidecar

Use the following example yaml to deploy an example helloworld container with a guard sidecar:

kubectl apply -f https://github.com/knative-sandbox/security-guard/releases/download/v0.1.2/secured-helloworld.yaml

Security alerts can be seen in the `guard-rproxy` container of the `secured-helloworld` pod using:

kubectl logs deployment/secured-helloworld guard-rproxy -f

## Deploying a separate Security-Guard Pod

Use the following example yaml to deploy one example `myapp` pod that include a container running helloworld, (this pod is not exposed outside of the cluster) and one `myapp-guard` pod that include a guard container to expose the myapp service outside the cluster while performing security-behavior monitoring and control on client requests:

kubectl apply -f https://github.com/knative-sandbox/security-guard/releases/download/v0.1.2/secured-layered-myapp.yaml

Security alerts can be seen in the `myapp-guard` pod using:

kubectl logs deployment/myapp-guard -f
21 changes: 21 additions & 0 deletions cmd/guard-rproxy/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
Copyright 2022 The Knative 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 main

// Uncomment when running in a development environment out side of the cluster
// import _ "k8s.io/client-go/plugin/pkg/client/auth"
// import _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
166 changes: 166 additions & 0 deletions cmd/guard-rproxy/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
Copyright 2022 The Knative 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 main

import (
"errors"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"os"
"runtime/debug"

"github.com/kelseyhightower/envconfig"
"go.uber.org/zap"

"knative.dev/pkg/signals"
_ "knative.dev/security-guard/pkg/guard-gate"
utils "knative.dev/security-guard/pkg/guard-utils"
pi "knative.dev/security-guard/pkg/pluginterfaces"
)

type config struct {
ServiceName string `split_words:"true" required:"true"`
Namespace string `split_words:"true" required:"true"`
ServiceUrl string `split_words:"true" required:"true"`
UseCrd bool `split_words:"true" required:"false"`
MonitorPod bool `split_words:"true" required:"false"`
GuardUrl string `split_words:"true" required:"false"`
LogLevel string `split_words:"true" required:"false"`
GuardProxyPort string `split_words:"true" required:"false"`
PodMonitorInterval string `split_words:"true" required:"false"`
ReportPileInterval string `split_words:"true" required:"false"`
GuardianLoadInterval string `split_words:"true" required:"false"`
}

type GuardGate struct {
nextRoundTripper http.RoundTripper // the next roundtripper
securityPlug pi.RoundTripPlug
}

func (p *GuardGate) RoundTrip(req *http.Request) (resp *http.Response, err error) {
defer func() {
if recovered := recover(); recovered != nil {
pi.Log.Warnf("Recovered from panic during RoundTrip! Recover: %v\n", recovered)
pi.Log.Debugf("Stacktrace from panic: \n %s\n" + string(debug.Stack()))
err = errors.New("paniced during RoundTrip")
resp = nil
}
}()
req.Host = "" // req.URL.Host

if req, err = p.securityPlug.ApproveRequest(req); err == nil {
if resp, err = p.nextRoundTripper.RoundTrip(req); err == nil {
resp, err = p.securityPlug.ApproveResponse(req, resp)
}
}
if err != nil {
pi.Log.Debugf("%s: returning error %v", p.securityPlug.PlugName(), err)
resp = nil
}
return
}

func (p *GuardGate) Transport(t http.RoundTripper) http.RoundTripper {
if t == nil {
t = http.DefaultTransport
}
p.nextRoundTripper = t
return p
}

func preMain() (guardGate *GuardGate, mux *http.ServeMux, target string, plugConfig map[string]string, sid string, ns string, log *zap.SugaredLogger) {
var env config
if err := envconfig.Process("", &env); err != nil {
fmt.Fprintf(os.Stderr, "Failed to process environment: %s\n", err.Error())
return
}

plugConfig = make(map[string]string)
guardGate = new(GuardGate)

log = utils.CreateLogger(env.LogLevel)
defer log.Sync()
pi.Log = log

if env.GuardUrl == "" {
// use default
env.GuardUrl = "http://guard-service.default"
} else {
plugConfig["guard-url"] = env.GuardUrl
}

// When using a Reverse Proxy, it has a default to not use pod monitoring
plugConfig["monitor-pod"] = "false" // default when used as a standalone
if env.MonitorPod {
plugConfig["monitor-pod"] = "true"
}

// When using a Reverse Proxy, it has a default to work with CM
plugConfig["use-cm"] = "true"
if env.UseCrd {
plugConfig["use-cm"] = "false"
}

plugConfig["guardian-load-interval"] = env.GuardianLoadInterval
plugConfig["report-pile-interval"] = env.ReportPileInterval
plugConfig["pod-monitor-interval"] = env.PodMonitorInterval

sid = env.ServiceName
ns = env.Namespace

log.Infof("guard-proxy serving serviceName: %s, namespace: %s, serviceUrl: %s", sid, ns, env.ServiceUrl)
parsedUrl, err := url.Parse(env.ServiceUrl)
if err != nil {
log.Errorf("Failed to parse serviceUrl: %s", err.Error())
return
}
log.Infof("guard-proxy parsedUrl: %v", parsedUrl)

proxy := httputil.NewSingleHostReverseProxy(parsedUrl)

// Hook using RoundTripper

securityPlug := pi.GetPlug()

guardGate.securityPlug = securityPlug
proxy.Transport = guardGate.Transport(proxy.Transport)

target = ":22000"
if env.GuardProxyPort != "" {
target = fmt.Sprintf(":%s", env.GuardProxyPort)
}

mux = http.NewServeMux()
mux.Handle("/", proxy)
log.Infof("Starting Reverse Proxy on port %s", target)
return
}

func main() {
guardGate, mux, target, plugConfig, sid, ns, log := preMain()
if mux == nil {
os.Exit(1)
}

guardGate.securityPlug.Init(signals.NewContext(), plugConfig, sid, ns, log)
defer guardGate.securityPlug.Shutdown()

err := http.ListenAndServe(target, mux)
log.Fatalf("Failed to open http local service: %s", err.Error())
}
99 changes: 99 additions & 0 deletions cmd/guard-rproxy/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
Copyright 2022 The Knative 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 main

import (
"os"
"testing"

_ "knative.dev/security-guard/pkg/guard-gate"
)

func Test_preMain(t *testing.T) {
tests := []struct {
name string
target string
mux bool
env map[string]string
}{
{
name: "missing env",
},
{
name: "missing service_url",
env: map[string]string{"SERVICE_NAME": "sid", "NAMESPACE": "ns"},
},
{
name: "missing service_sid",
env: map[string]string{"SERVICE_URL": "http://127.0.0.1:80", "NAMESPACE": "ns"},
},
{
name: "missing service_ns",
env: map[string]string{"SERVICE_NAME": "sid", "SERVICE_URL": "http://127.0.0.1:80"},
},
{
name: "envok",
env: map[string]string{"SERVICE_NAME": "sid", "NAMESPACE": "ns", "SERVICE_URL": "http://127.0.0.1:80"},
mux: true,
target: ":22000",
},
{
name: "fullenv",
env: map[string]string{
"SERVICE_NAME": "sid",
"NAMESPACE": "ns",
"SERVICE_URL": "http://127.0.0.1:81",
"GUARD_URL": "http://127.0.0.1:82",
"MONITOR_POD": "true",
"USE_CRD": "true",
"GUARD_PROXY_PORT": "8888",
},
mux: true,
target: ":8888",
},
{
name: "wrongenv",
env: map[string]string{
"SERVICE_NAME": "sid",
"NAMESPACE": "ns",
"SERVICE_URL": "http://user:abc{DEf1=ghi@example.com:5432",
"GUARD_URL": "http://user:abc{DEf1=ghi@example.com:5432",
"MONITOR_POD": "true",
"USE_CRD": "true",
"GUARD_PROXY_PORT": "88881",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for k, v := range tt.env {
os.Setenv(k, v)
}
//guardGate, mux, target, plugConfig, sid, ns, log := preMain()
_, mux, target, _, _, _, _ := preMain()
if (mux != nil) != tt.mux {
t.Errorf("preMain() mux expected %t, received %t", tt.mux, mux != nil)
}
if target != tt.target {
t.Errorf("preMain() target expected %v, received %v", tt.target, target)
}
for k := range tt.env {
os.Unsetenv(k)
}
})
}
}
Loading

0 comments on commit 4459d49

Please sign in to comment.