Skip to content
This repository has been archived by the owner on Jul 11, 2023. It is now read-only.

Commit

Permalink
feat(externAuthz): bring external authorization to 0.8
Browse files Browse the repository at this point in the history
This commit bring configurable inbound external authorization
to OSM v0.8.

Signed-off-by: Eduard Serra <[email protected]>
  • Loading branch information
eduser25 committed May 7, 2021
1 parent 6fa4a57 commit 138f735
Show file tree
Hide file tree
Showing 19 changed files with 496 additions and 32 deletions.
1 change: 1 addition & 0 deletions charts/osm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ The following table lists the configurable parameters of the osm chart and their
| OpenServiceMesh.image.registry | string | `"openservicemesh"` | `osm-controller` image registry |
| OpenServiceMesh.image.tag | string | `"v0.8.3"` | `osm-controller` image tag |
| OpenServiceMesh.imagePullSecrets | list | `[]` | `osm-controller` image pull secret |
| OpenServiceMesh.inbound_extauthz | object | `{"address":"","enable":false,"failureModeAllow":false,"port":0,"statPrefix":"","timeout":"1s"}` | Inbound external authorization, allows configuring a remote service to provide external authorization upon request |
| OpenServiceMesh.injector | object | `{"podLabels":{},"replicaCount":1,"resource":{"limits":{"cpu":"0.5","memory":"64M"},"requests":{"cpu":"0.3","memory":"64M"}}}` | Sidecar injector configuration |
| OpenServiceMesh.meshName | string | `"osm"` | Name for the new control plane instance |
| OpenServiceMesh.osmNamespace | string | `""` | Optional parameter. If not specified, the release namespace is used to deploy the osm components. |
Expand Down
9 changes: 9 additions & 0 deletions charts/osm/templates/osm-configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ data:
enable_debug_server: {{ .Values.OpenServiceMesh.enableDebugServer | quote }}
prometheus_scraping: {{ .Values.OpenServiceMesh.enablePrometheusScraping | quote }}

inbound_extauthz_enable: {{ .Values.OpenServiceMesh.inbound_extauthz.enable | quote }}
{{- if .Values.OpenServiceMesh.inbound_extauthz.enable }}
inbound_extauthz_address: {{ .Values.OpenServiceMesh.inbound_extauthz.address | quote }}
inbound_extauthz_port: {{ .Values.OpenServiceMesh.inbound_extauthz.port | quote }}
inbound_extauthz_statprefix: {{ .Values.OpenServiceMesh.inbound_extauthz.statPrefix | quote }}
inbound_extauthz_timeout: {{ .Values.OpenServiceMesh.inbound_extauthz.timeout | quote }}
inbound_extauthz_failuremodeallow: {{ .Values.OpenServiceMesh.inbound_extauthz.failureModeAllow | quote }}
{{- end }}

tracing_enable: {{ .Values.OpenServiceMesh.tracing.enable | quote }}
{{- if .Values.OpenServiceMesh.tracing.enable }}
tracing_address: {{ include "osm.tracingAddress" . | quote }}
Expand Down
10 changes: 10 additions & 0 deletions charts/osm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,16 @@ OpenServiceMesh:
# -- Destination's API or collector endpoint where the spans will be sent to
endpoint: "/api/v2/spans"

# -- Inbound external authorization, allows configuring a remote service to provide
# external authorization upon request
inbound_extauthz:
enable: false
address: ""
port: 0
statPrefix: ""
timeout: 1s
failureModeAllow: false

# -- Optional parameter to specify a global list of IP ranges to exclude from outbound traffic interception by the sidecar proxy.
# If specified, must be a list of IP ranges of the form a.b.c.d/x.
outboundIPRangeExclusionList: []
Expand Down
119 changes: 119 additions & 0 deletions docs/content/docs/external_auth_opa.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# OPA-plugin-enabled OSM POC

## Overview and limitations
- Allows configuring an envoy's [External Authorization extension](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter) through OSM's configmap.
- Authorization filter is currently applied in inmesh `inbound` and `ingress` connections.
- This demo DOES NOT inject the OPA plugin sidecar on every pod, though this seems to be the intended model for OPA to run with for obvious latency reasons.

If using `values.yaml`:
```
# External authz
inbound_extauthz:
enable: true
address: opa.opa.svc.cluster.local
port: 9191
statPrefix: authz_opa
timeout: 1s
failureModeAllow: false
```
Example OSM's configmap deployed by this POC:
```
inbound_extauthz_enable: true
inbound_extauthz_address: opa.opa.svc.cluster.local
inbound_extauthz_port: 9191
inbound_extauthz_statprefix: authz_opa
inbound_extauthz_timeout: 1s
inbound_extauthz_failuremodeallow: false
```

- Uses (as `opa-envoy-plugin`) a configmap to set OPA's policy. [This is not intended for production](https://github.com/open-policy-agent/opa-envoy-plugin#example-bundle-configuration).
- `opa-envoy-plugin` does not seem to react to changes on the configmap. To update policies, currently the OPA container needs to be restarted.

## Demo Walkthrough

- Deploy an `opa-envoy-plugin`, use curated yaml in this folder.
- NOTE: This POC is using a single plugin to handle all pod connections, and through the network. This is not intended for production.
```
kubectl create namespace opa
kubectl apply -f docs/example/manifests/opa/deploy-opa-envoy.yaml
```
`deploy-opa-envoy.yaml` will deploy OPA's envoy plugin and run it as a service, allowing external authorization calls from envoys through the network.

- Deploy OSM's Demo, follow `demo/run-osm-demo.sh`
```
demo/run-osm-demo.sh # wait for all services to come up
```
- This branch has an External Authorization server expected by default at `opa.opa.svc.cluster.local:9191`. Timeout for authorization RTT is `1s` by default, and will not allow traffic in case of failure.

Traffic should fail right out of the bat:
```
kubectl logs <bookbuyer_pod> -n bookbuyer bookbuyer
```
```
...
--- bookbuyer:[ 8 ] -----------------------------------------
Fetching http://bookstore.bookstore:14001/books-bought
Request Headers: map[Client-App:[bookbuyer] User-Agent:[Go-http-client/1.1]]
Identity: n/a
Booksbought: n/a
Server: envoy
Date: Tue, 04 May 2021 01:20:39 GMT
Status: 403 Forbidden
ERROR: response code for "http://bookstore.bookstore:14001/books-bought" is 403; expected 200
...
```

You shoud also be able to see the logs in `opa-envoy-plugin` for rejected authorization calls:
```
kubectl logs <opa_pod> -n opa
```
```
{"decision_id":"1df154b5-658a-47bf-ac18-be52998605da","input":{"attributes":{"destination":{"address":{"socketAddress":{"address":"10.0.16.44","portValue":14001}}},"metadataContext":{},"request":{"http":{"headers":{":authority":"bookstore.bookstore:14001",":method":"GET",":path":"/books-bought","accept-encoding":"gzip","client-app":"bookbuyer","user-agent":"Go-http-client/1.1","x-forwarded-proto":"http","x-request-id":"69b80716-6af4-4986-bf8c-8f209d96f131"},"host":"bookstore.bookstore:14001","id":"6079090369556950701","method":"GET","path":"/books-bought","protocol":"HTTP/1.1"},"time":"2021-05-04T01:21:18.195876Z"},"source":{"address":{"socketAddress":{"address":"10.244.2.10","portValue":53488}}}},"parsed_body":null,"parsed_path":["books-bought"],"parsed_query":{},"truncated_body":false,"version":{"encoding":"protojson","ext_authz":"v3"}},"labels":{"id":"6e56bc11-a212-4c3e-be4d-b33186fd581d","version":"0.28.0-envoy"},"level":"info","metrics":{"timer_rego_query_eval_ns":105799,"timer_server_handler_ns":425097},"msg":"Decision Log","path":"envoy/authz/allow","requested_by":"","result":false,"time":"2021-05-04T01:21:18Z","timestamp":"2021-05-04T01:21:18.1971808Z","type":"openpolicyagent.org/decision_logs"}
```

- Now edit OPA's policy:
```
kubectl edit configmap opa-policy -n opa
```
change specifically the default-all from:
```
default allow = false
```
to
```
default allow = true
```

- Finally, restart `opa-envoy-plugin`:
```
kubectl rollout restart deployment opa -n opa
```

- Observe that bookbuyer calls are now being allowed:
```
--- bookbuyer:[ 2663 ] -----------------------------------------
Fetching http://bookstore.bookstore:14001/books-bought
Request Headers: map[Client-App:[bookbuyer] User-Agent:[Go-http-client/1.1]]
Identity: bookstore-v1
Booksbought: 1087
Server: envoy
Date: Tue, 04 May 2021 02:00:46 GMT
Status: 200 OK
MAESTRO! THIS TEST SUCCEEDED!
Fetching http://bookstore.bookstore:14001/buy-a-book/new
Request Headers: map[]
Identity: bookstore-v1
Booksbought: 1088
Server: envoy
Date: Tue, 04 May 2021 02:00:47 GMT
Status: 200 OK
ESC[90m2:00AMESC[0m ESC[32mINFESC[0m BooksCountV1=21490056 ESC[36mcomponent=ESC[0mdemo ESC[36mfile=ESC[0mbooks.go:167
MAESTRO! THIS TEST SUCCEEDED!
```

```
{"decision_id":"3f29d449-7f71-4721-b93c-ad7d375e0f80","input":{"attributes":{"destination":{"address":{"socketAddress":{"address":"10.0.16.44","portValue":14001}}},"metadataContext":{},"request":{"http":{"headers":{":authority":"bookstore.bookstore:14001",":method":"GET",":path":"/buy-a-book/new","accept-encoding":"gzip","user-agent":"Go-http-client/1.1","x-forwarded-proto":"http","x-request-id":"97bd9339-448f-4710-bba4-bda3b5103aa0"},"host":"bookstore.bookstore:14001","id":"14741973070759351541","method":"GET","path":"/buy-a-book/new","protocol":"HTTP/1.1"},"time":"2021-05-04T02:01:35.813125Z"},"source":{"address":{"socketAddress":{"address":"10.244.2.10","portValue":48860}}}},"parsed_body":null,"parsed_path":["buy-a-book","new"],"parsed_query":{},"truncated_body":false,"version":{"encoding":"protojson","ext_authz":"v3"}},"labels":{"id":"e8f143eb-9edf-425d-9210-340001993841","version":"0.28.0-envoy"},"level":"info","metrics":{"timer_rego_query_eval_ns":50500,"timer_server_handler_ns":370797},"msg":"Decision Log","path":"envoy/authz/allow","requested_by":"","result":true,"time":"2021-05-04T02:01:35Z","timestamp":"2021-05-04T02:01:35.816768454Z","type":"openpolicyagent.org/decision_logs"}
```
99 changes: 99 additions & 0 deletions docs/example/manifests/opa/deploy-opa-envoy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
####################################################
# App Deployment with OPA-Envoy and Envoy sidecars.
####################################################
apiVersion: apps/v1
kind: Deployment
metadata:
name: opa
namespace: opa
labels:
app: opa
spec:
replicas: 1
selector:
matchLabels:
app: opa
template:
metadata:
labels:
app: opa
spec:
containers:
- name: opa-envoy
image: openpolicyagent/opa:0.28.0-envoy
securityContext:
runAsUser: 1111
volumeMounts:
- readOnly: true
mountPath: /policy
name: opa-policy
- readOnly: true
mountPath: /config
name: opa-envoy-config
args:
- "run"
- "--server"
- "--config-file=/config/config.yaml"
- "--addr=0.0.0.0:8181"
- "--diagnostic-addr=0.0.0.0:8282"
- "--ignore=.*"
- "/policy/policy.rego"
volumes:
- name: proxy-config
configMap:
name: proxy-config
- name: opa-policy
configMap:
name: opa-policy
- name: opa-envoy-config
configMap:
name: opa-envoy-config
---
############################################################
# Example configuration to bootstrap OPA-Envoy sidecars.
############################################################
apiVersion: v1
kind: ConfigMap
metadata:
name: opa-envoy-config
namespace: opa
data:
config.yaml: |
plugins:
envoy_ext_authz_grpc:
addr: :9191
path: envoy/authz/allow
decision_logs:
console: true
---
apiVersion: v1
kind: Service
metadata:
name: opa
namespace: opa
labels:
app: opa
spec:
ports:
- port: 9191
protocol: TCP
targetPort: 9191
selector:
app: opa
---
############################################################
# Example policy to enforce into OPA-Envoy sidecars.
############################################################
apiVersion: v1
kind: ConfigMap
metadata:
name: opa-policy
namespace: opa
data:
policy.rego: |
package envoy.authz
import input.attributes.request.http as http_request
default allow = false
---
42 changes: 42 additions & 0 deletions pkg/configurator/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ const (

// configResyncInterval is the key name used to configure the resync interval for regular proxy broadcast updates
configResyncInterval = "config_resync_interval"

// inboundExtauthzEnable is the key used to enable the inbound external authorization filter
inboundExtauthzEnable = "inbound_extauthz_enable"

// inboundExtauthzAddress is the key used to specify external authorization address
inboundExtauthzAddress = "inbound_extauthz_address"

// inboundExtauthzPort is the key used to specify external authorization port
inboundExtauthzPort = "inbound_extauthz_port"

// inboundExtauthzStatPrefix is the key used to specify external authorization stats prefix
inboundExtauthzStatPrefix = "inbound_extauthz_statprefix"

// inboundExtauthzTimeout is the key used to specify external authorization connection timeout
inboundExtauthzTimeout = "inbound_extauthz_timeout"

// inboundExtauthzFailureModeAllow is the key used to specify external authorization failure mode
inboundExtauthzFailureModeAllow = "inbound_extauthz_failuremodeallow"
)

// NewConfigurator implements configurator.Configurator and creates the Kubernetes client to manage namespaces.
Expand Down Expand Up @@ -163,6 +181,13 @@ func (c *Client) configMapListener() {
triggerGlobalBroadcast = triggerGlobalBroadcast || (prevConfigMap.TracingAddress != newConfigMap.TracingAddress)
triggerGlobalBroadcast = triggerGlobalBroadcast || (prevConfigMap.TracingEndpoint != newConfigMap.TracingEndpoint)
triggerGlobalBroadcast = triggerGlobalBroadcast || (prevConfigMap.TracingPort != newConfigMap.TracingPort)
triggerGlobalBroadcast = triggerGlobalBroadcast || (prevConfigMap.PrometheusScraping != newConfigMap.PrometheusScraping)
triggerGlobalBroadcast = triggerGlobalBroadcast || (prevConfigMap.InboundExternAuthzAddress != newConfigMap.InboundExternAuthzAddress)
triggerGlobalBroadcast = triggerGlobalBroadcast || (prevConfigMap.InboundExternAuthzStatPrefix != newConfigMap.InboundExternAuthzStatPrefix)
triggerGlobalBroadcast = triggerGlobalBroadcast || (prevConfigMap.InboundExternAuthzTimeout != newConfigMap.InboundExternAuthzTimeout)
triggerGlobalBroadcast = triggerGlobalBroadcast || (prevConfigMap.InboundExternAuthzEnable != newConfigMap.InboundExternAuthzEnable)
triggerGlobalBroadcast = triggerGlobalBroadcast || (prevConfigMap.InboundExternAuthzFailureModeAllow != newConfigMap.InboundExternAuthzFailureModeAllow)
triggerGlobalBroadcast = triggerGlobalBroadcast || (prevConfigMap.InboundExternAuthzPort != newConfigMap.InboundExternAuthzPort)

if triggerGlobalBroadcast {
log.Debug().Msgf("[%s] OSM ConfigMap update triggered global proxy broadcast",
Expand Down Expand Up @@ -229,6 +254,14 @@ type osmConfig struct {

// ConfigResyncInterval is a flag to configure resync interval for regular proxy broadcast updates
ConfigResyncInterval string `yaml:"config_resync_interval"`

// Inbound external authorization flags, as specified by keys and explained above
InboundExternAuthzEnable bool `yaml:"inbound_extauthz_enable"`
InboundExternAuthzAddress string `yaml:"inbound_extauthz_address"`
InboundExternAuthzPort int `yaml:"inbound_extauthz_port"`
InboundExternAuthzStatPrefix string `yaml:"inbound_extauthz_statprefix"`
InboundExternAuthzTimeout string `yaml:"inbound_extauthz_timeout"`
InboundExternAuthzFailureModeAllow bool `yaml:"inbound_extauthz_failuremodeallow"`
}

func (c *Client) run(stop <-chan struct{}) {
Expand Down Expand Up @@ -278,6 +311,7 @@ func parseOSMConfigMap(configMap *v1.ConfigMap) *osmConfig {
osmConfigMap.PrometheusScraping, _ = GetBoolValueForKey(configMap, prometheusScrapingKey)
osmConfigMap.UseHTTPSIngress, _ = GetBoolValueForKey(configMap, useHTTPSIngressKey)
osmConfigMap.TracingEnable, _ = GetBoolValueForKey(configMap, tracingEnableKey)
osmConfigMap.InboundExternAuthzEnable, _ = GetBoolValueForKey(configMap, inboundExtauthzEnable)
osmConfigMap.EnvoyLogLevel, _ = GetStringValueForKey(configMap, envoyLogLevel)
osmConfigMap.ServiceCertValidityDuration, _ = GetStringValueForKey(configMap, serviceCertValidityDurationKey)
osmConfigMap.OutboundIPRangeExclusionList, _ = GetStringValueForKey(configMap, outboundIPRangeExclusionListKey)
Expand All @@ -290,6 +324,14 @@ func parseOSMConfigMap(configMap *v1.ConfigMap) *osmConfig {
osmConfigMap.TracingEndpoint, _ = GetStringValueForKey(configMap, tracingEndpointKey)
}

if osmConfigMap.InboundExternAuthzEnable {
osmConfigMap.InboundExternAuthzAddress, _ = GetStringValueForKey(configMap, inboundExtauthzAddress)
osmConfigMap.InboundExternAuthzPort, _ = GetIntValueForKey(configMap, inboundExtauthzPort)
osmConfigMap.InboundExternAuthzStatPrefix, _ = GetStringValueForKey(configMap, inboundExtauthzStatPrefix)
osmConfigMap.InboundExternAuthzTimeout, _ = GetStringValueForKey(configMap, inboundExtauthzTimeout)
osmConfigMap.InboundExternAuthzFailureModeAllow, _ = GetBoolValueForKey(configMap, inboundExtauthzFailureModeAllow)
}

return &osmConfigMap
}

Expand Down
34 changes: 20 additions & 14 deletions pkg/configurator/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,26 @@ var _ = Describe("Test OSM ConfigMap parsing", func() {

It("Tag matches const key for all fields of OSM ConfigMap struct", func() {
fieldNameTag := map[string]string{
"PermissiveTrafficPolicyMode": PermissiveTrafficPolicyModeKey,
"Egress": egressKey,
"EnableDebugServer": enableDebugServer,
"PrometheusScraping": prometheusScrapingKey,
"TracingEnable": tracingEnableKey,
"TracingAddress": tracingAddressKey,
"TracingPort": tracingPortKey,
"TracingEndpoint": tracingEndpointKey,
"UseHTTPSIngress": useHTTPSIngressKey,
"EnvoyLogLevel": envoyLogLevel,
"ServiceCertValidityDuration": serviceCertValidityDurationKey,
"OutboundIPRangeExclusionList": outboundIPRangeExclusionListKey,
"EnablePrivilegedInitContainer": enablePrivilegedInitContainer,
"ConfigResyncInterval": configResyncInterval,
"PermissiveTrafficPolicyMode": PermissiveTrafficPolicyModeKey,
"Egress": egressKey,
"EnableDebugServer": enableDebugServer,
"PrometheusScraping": prometheusScrapingKey,
"TracingEnable": tracingEnableKey,
"TracingAddress": tracingAddressKey,
"TracingPort": tracingPortKey,
"TracingEndpoint": tracingEndpointKey,
"UseHTTPSIngress": useHTTPSIngressKey,
"EnvoyLogLevel": envoyLogLevel,
"ServiceCertValidityDuration": serviceCertValidityDurationKey,
"OutboundIPRangeExclusionList": outboundIPRangeExclusionListKey,
"EnablePrivilegedInitContainer": enablePrivilegedInitContainer,
"ConfigResyncInterval": configResyncInterval,
"InboundExternAuthzEnable": inboundExtauthzEnable,
"InboundExternAuthzAddress": inboundExtauthzAddress,
"InboundExternAuthzPort": inboundExtauthzPort,
"InboundExternAuthzStatPrefix": inboundExtauthzStatPrefix,
"InboundExternAuthzTimeout": inboundExtauthzTimeout,
"InboundExternAuthzFailureModeAllow": inboundExtauthzFailureModeAllow,
}
t := reflect.TypeOf(osmConfig{})

Expand Down
Loading

0 comments on commit 138f735

Please sign in to comment.