Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support policies in VS routes and VSR subroutes #1058

Merged
merged 1 commit into from
Jul 16, 2020
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
11 changes: 11 additions & 0 deletions deployments/common/vs-definition.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,17 @@ spec:
type: integer
path:
type: string
policies:
type: array
items:
description: PolicyReference references a policy by name and
an optional namespace.
type: object
properties:
name:
type: string
namespace:
type: string
route:
type: string
splits:
Expand Down
11 changes: 11 additions & 0 deletions deployments/common/vsr-definition.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,17 @@ spec:
type: integer
path:
type: string
policies:
type: array
items:
description: PolicyReference references a policy by name and
an optional namespace.
type: object
properties:
name:
type: string
namespace:
type: string
route:
type: string
splits:
Expand Down
11 changes: 11 additions & 0 deletions deployments/helm-chart/crds/virtualserver.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,17 @@ spec:
type: integer
path:
type: string
policies:
type: array
items:
description: PolicyReference references a policy by name and
an optional namespace.
type: object
properties:
name:
type: string
namespace:
type: string
route:
type: string
splits:
Expand Down
11 changes: 11 additions & 0 deletions deployments/helm-chart/crds/virtualserverroute.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,17 @@ spec:
type: integer
path:
type: string
policies:
type: array
items:
description: PolicyReference references a policy by name and
an optional namespace.
type: object
properties:
name:
type: string
namespace:
type: string
route:
type: string
splits:
Expand Down
6 changes: 3 additions & 3 deletions docs-web/configuration/policy-resource.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Policy Resource

The Policy resource allows you to configure features like authentication, rate-limiting, and WAF, which you can add to your [VirtualServer resources](/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/). In the initial release, we are introducing support for access control based on the client IP address.
The Policy resource allows you to configure features like authentication, rate-limiting, and WAF, which you can add to your [VirtualServer and VirtualServerRoute resources](/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/). In the initial release, we are introducing support for access control based on the client IP address.

The resource is implemented as a [Custom Resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/).

Expand All @@ -23,7 +23,7 @@ This document is the reference documentation for the Policy resource. An example

## Prerequisites

Policies work together with [VirtualServer resources](/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/), which you need to create separately.
Policies work together with [VirtualServer and VirtualServerRoute resources](/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/), which you need to create separately.

## Policy Specification

Expand Down Expand Up @@ -94,7 +94,7 @@ accessControl:

#### AccessControl Merging Behavior

A VirtualServer can reference multiple access control policies. For example, here we reference two policies, each with configured allow lists:
A VirtualServer/VirtualServerRoute can reference multiple access control policies. For example, here we reference two policies, each with configured allow lists:
```yaml
policies:
- name: allow-policy-one
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ The route defines rules for matching client requests to actions like passing a r
- The path of the route. NGINX will match it against the URI of a request. Possible values are: a prefix (\ ``/``\ , ``/path``\ ), an exact match (\ ``=/exact/match``\ ), a case insensitive regular expression (\ ``~*^/Bar.*\\.jpg``\ ) or a case sensitive regular expression (\ ``~^/foo.*\\.jpg``\ ). In the case of a prefix (must start with ``/``\ ) or an exact match (must start with ``=``\ ), the path must not include any whitespace characters, ``{``\ , ``}`` or ``;``. In the case of the regex matches, all double quotes ``"`` must be escaped and the match can't end in an unescaped backslash ``\``. The path must be unique among the paths of all routes of the VirtualServer. Check the `location <https://nginx.org/en/docs/http/ngx_http_core_module.html#location>`_ directive for more information.
- ``string``
- Yes
* - ``policies``
- A list of policies. The policies override the policies of the same type defined in the ``spec`` of the VirtualServer. The overriding is done by NGINX: the route policies are configured in the ``location`` context, which overrides the spec policies of the same type defined in the ``server`` context.
- `[]policy <#virtualserver-policy>`_
- No
* - ``action``
- The default action to perform for a request.
- `action <#action>`_
Expand Down Expand Up @@ -346,6 +350,10 @@ action:
- The path of the subroute. NGINX will match it against the URI of a request. Possible values are: a prefix (\ ``/``\ , ``/path``\ ), an exact match (\ ``=/exact/match``\ ), a case insensitive regular expression (\ ``~*^/Bar.*\\.jpg``\ ) or a case sensitive regular expression (\ ``~^/foo.*\\.jpg``\ ). In the case of a prefix, the path must start with the same path as the path of the route of the VirtualServer that references this resource. In the case of an exact or regex match, the path must be the same as the path of the route of the VirtualServer that references this resource. In the case of a prefix or an exact match, the path must not include any whitespace characters, ``{``\ , ``}`` or ``;``. In the case of the regex matches, all double quotes ``"`` must be escaped and the match can't end in an unescaped backslash ``\``. The path must be unique among the paths of all subroutes of the VirtualServerRoute.
- ``string``
- Yes
* - ``policies``
- A list of policies. The policies override *all* policies defined in the route of the VirtualServer that references this resource. This is done by the Ingress Controller: the route policies of the VirtualServer will not be present in the generated configuration. The policies also override the policies of the same type defined in the ``spec`` of the VirtualServer. This overriding is done by NGINX: the subroute policies are configured in the ``location`` context, which overrides the spec policies of the same type defined in the ``server`` context.
- `[]policy <#virtualserver-policy>`_
- No
* - ``action``
- The default action to perform for a request.
- `action <#action>`_
Expand Down
2 changes: 1 addition & 1 deletion docs-web/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Events:
```
Note that in the events section, we have a `Normal` event with the `AddedOrUpdated` reason, which informs us that the policy was successfully accepted.

However, the fact that a policy was accepted doesn't guarantee that the NGINX configuration was successfully applied. To confirm that, check the events of the VirtualServer resources that reference that policy.
However, the fact that a policy was accepted doesn't guarantee that the NGINX configuration was successfully applied. To confirm that, check the events of the VirtualServer and VirtualServerRoute resources that reference that policy.

### Checking the Events of the ConfigMap Resource

Expand Down
3 changes: 3 additions & 0 deletions internal/configs/version2/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ type Location struct {
ErrorPages []ErrorPage
ProxySSLName string
InternalProxyPass string
Allow []string
Deny []string
PoliciesErrorReturn *Return
}

// ReturnLocation defines a location for returning a fixed response.
Expand Down
18 changes: 18 additions & 0 deletions internal/configs/version2/nginx-plus.virtualserver.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,24 @@ server {
{{ $snippet }}
{{ end }}

{{ with $l.PoliciesErrorReturn }}
return {{ .Code }};
{{ end }}

{{ range $allow := $l.Allow }}
allow {{ $allow }};
{{ end }}
{{ if gt (len $l.Allow) 0 }}
deny all;
{{ end }}

{{ range $deny := $l.Deny }}
deny {{ $deny }};
{{ end }}
{{ if gt (len $l.Deny) 0 }}
allow all;
{{ end }}

{{ range $e := $l.ErrorPages }}
error_page {{ $e.Codes }} {{ if ne 0 $e.ResponseCode }}={{ $e.ResponseCode }}{{ end }} "{{ $e.Name }}";
{{ end }}
Expand Down
18 changes: 18 additions & 0 deletions internal/configs/version2/nginx.virtualserver.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,24 @@ server {
{{ $snippet }}
{{ end }}

{{ with $l.PoliciesErrorReturn }}
return {{ .Code }};
{{ end }}

{{ range $allow := $l.Allow }}
allow {{ $allow }};
{{ end }}
{{ if gt (len $l.Allow) 0 }}
deny all;
{{ end }}

{{ range $deny := $l.Deny }}
deny {{ $deny }};
{{ end }}
{{ if gt (len $l.Deny) 0 }}
allow all;
{{ end }}

{{ range $e := $l.ErrorPages }}
error_page {{ $e.Codes }} {{ if ne 0 $e.ResponseCode }}={{ $e.ResponseCode }}{{ end }} "{{ $e.Name }}";
{{ end }}
Expand Down
2 changes: 2 additions & 0 deletions internal/configs/version2/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ var virtualServerCfg = VirtualServerConfig{
{
Path: "/",
Snippets: []string{"# location snippet"},
Allow: []string{"127.0.0.1"},
Deny: []string{"127.0.0.1"},
ProxyConnectTimeout: "30s",
ProxyReadTimeout: "31s",
ProxySendTimeout: "32s",
Expand Down
60 changes: 49 additions & 11 deletions internal/configs/virtualserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,9 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE
var maps []version2.Map
var errorPageLocations []version2.ErrorPageLocation
var vsrErrorPagesFromVs = make(map[string][]conf_v1.ErrorPage)
var vsrLocationSnippetsFromVs = make(map[string]string)
var vsrErrorPagesRouteIndex = make(map[string]int)
var vsrLocationSnippetsFromVs = make(map[string]string)
var vsrPoliciesFromVs = make(map[string][]conf_v1.PolicyReference)
matchesRoutes := 0

variableNamer := newVariableNamer(virtualServerEx.VirtualServer)
Expand All @@ -270,32 +271,39 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE

// ignore routes that reference VirtualServerRoute
if r.Route != "" {
name := r.Route
if !strings.Contains(name, "/") {
name = fmt.Sprintf("%v/%v", virtualServerEx.VirtualServer.Namespace, r.Route)
}

// store route location snippet for the referenced VirtualServerRoute in case they don't define their own
if r.LocationSnippets != "" {
name := r.Route
if !strings.Contains(name, "/") {
name = fmt.Sprintf("%v/%v", virtualServerEx.VirtualServer.Namespace, r.Route)
}
vsrLocationSnippetsFromVs[name] = r.LocationSnippets
}

// store route error pages and route index for the referenced VirtualServerRoute in case they don't define their own
if len(r.ErrorPages) > 0 {
name := r.Route
if !strings.Contains(name, "/") {
name = fmt.Sprintf("%v/%v", virtualServerEx.VirtualServer.Namespace, r.Route)
}
vsrErrorPagesFromVs[name] = r.ErrorPages
vsrErrorPagesRouteIndex[name] = errorPageIndex
}

// store route policies for the referenced VirtualServerRoute in case they don't define their own
if len(r.Policies) > 0 {
vsrPoliciesFromVs[name] = r.Policies
}

continue
}

vsLocSnippets := r.LocationSnippets
routePoliciesCfg := vsc.generatePolicies(virtualServerEx.VirtualServer, virtualServerEx.VirtualServer.Namespace,
r.Policies, virtualServerEx.Policies)

if len(r.Matches) > 0 {
cfg := generateMatchesConfig(r, virtualServerUpstreamNamer, crUpstreams, variableNamer, matchesRoutes, len(splitClients),
vsc.cfgParams, r.ErrorPages, errorPageIndex, vsLocSnippets, vsc.enableSnippets, len(returnLocations))
addPoliciesCfgToLocations(routePoliciesCfg, cfg.Locations)

maps = append(maps, cfg.Maps...)
locations = append(locations, cfg.Locations...)
internalRedirectLocations = append(internalRedirectLocations, cfg.InternalRedirectLocation)
Expand All @@ -305,6 +313,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE
} else if len(r.Splits) > 0 {
cfg := generateDefaultSplitsConfig(r, virtualServerUpstreamNamer, crUpstreams, variableNamer, len(splitClients),
vsc.cfgParams, r.ErrorPages, errorPageIndex, r.Path, vsLocSnippets, vsc.enableSnippets, len(returnLocations))
addPoliciesCfgToLocations(routePoliciesCfg, cfg.Locations)

splitClients = append(splitClients, cfg.SplitClients...)
locations = append(locations, cfg.Locations...)
Expand All @@ -314,8 +323,11 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE
upstreamName := virtualServerUpstreamNamer.GetNameForUpstreamFromAction(r.Action)
upstream := crUpstreams[upstreamName]
proxySSLName := generateProxySSLName(upstream.Service, virtualServerEx.VirtualServer.Namespace)

loc, returnLoc := generateLocation(r.Path, upstreamName, upstream, r.Action, vsc.cfgParams, r.ErrorPages, false,
errorPageIndex, proxySSLName, r.Path, vsLocSnippets, vsc.enableSnippets, len(returnLocations))
addPoliciesCfgToLocation(routePoliciesCfg, &loc)

locations = append(locations, loc)
if returnLoc != nil {
returnLocations = append(returnLocations, *returnLoc)
Expand All @@ -331,7 +343,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE
errorPageLocations = append(errorPageLocations, generateErrorPageLocations(errorPageIndex, r.ErrorPages)...)
errorPages := r.ErrorPages
vsrNamespaceName := fmt.Sprintf("%v/%v", vsr.Namespace, vsr.Name)
// use referenced VirtualServer error pages if the route does not define any
// use the VirtualServer error pages if the route does not define any
if r.ErrorPages == nil {
if vsErrorPages, ok := vsrErrorPagesFromVs[vsrNamespaceName]; ok {
errorPages = vsErrorPages
Expand All @@ -340,14 +352,23 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE
}

locSnippets := r.LocationSnippets
// use referenced VirtualServer location snippet if the route does not define any
// use the VirtualServer location snippet if the route does not define any
if r.LocationSnippets == "" {
locSnippets = vsrLocationSnippetsFromVs[vsrNamespaceName]
}

routePoliciesCfg := vsc.generatePolicies(vsr, vsr.Namespace, r.Policies, virtualServerEx.Policies)
// use the VirtualServer route policies if the route does not define any
if len(r.Policies) == 0 {
routePoliciesCfg = vsc.generatePolicies(virtualServerEx.VirtualServer, virtualServerEx.VirtualServer.Namespace,
vsrPoliciesFromVs[vsrNamespaceName], virtualServerEx.Policies)
}

if len(r.Matches) > 0 {
cfg := generateMatchesConfig(r, upstreamNamer, crUpstreams, variableNamer, matchesRoutes, len(splitClients),
vsc.cfgParams, errorPages, errorPageIndex, locSnippets, vsc.enableSnippets, len(returnLocations))
addPoliciesCfgToLocations(routePoliciesCfg, cfg.Locations)

maps = append(maps, cfg.Maps...)
locations = append(locations, cfg.Locations...)
internalRedirectLocations = append(internalRedirectLocations, cfg.InternalRedirectLocation)
Expand All @@ -357,6 +378,8 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE
} else if len(r.Splits) > 0 {
cfg := generateDefaultSplitsConfig(r, upstreamNamer, crUpstreams, variableNamer, len(splitClients), vsc.cfgParams,
errorPages, errorPageIndex, r.Path, locSnippets, vsc.enableSnippets, len(returnLocations))
addPoliciesCfgToLocations(routePoliciesCfg, cfg.Locations)

splitClients = append(splitClients, cfg.SplitClients...)
locations = append(locations, cfg.Locations...)
internalRedirectLocations = append(internalRedirectLocations, cfg.InternalRedirectLocation)
Expand All @@ -365,8 +388,11 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE
upstreamName := upstreamNamer.GetNameForUpstreamFromAction(r.Action)
upstream := crUpstreams[upstreamName]
proxySSLName := generateProxySSLName(upstream.Service, vsr.Namespace)

loc, returnLoc := generateLocation(r.Path, upstreamName, upstream, r.Action, vsc.cfgParams, errorPages, false,
errorPageIndex, proxySSLName, r.Path, locSnippets, vsc.enableSnippets, len(returnLocations))
addPoliciesCfgToLocation(routePoliciesCfg, &loc)

locations = append(locations, loc)
if returnLoc != nil {
returnLocations = append(returnLocations, *returnLoc)
Expand Down Expand Up @@ -459,6 +485,18 @@ func (vsc *virtualServerConfigurator) generatePolicies(owner runtime.Object, own
}
}

func addPoliciesCfgToLocation(cfg policiesCfg, location *version2.Location) {
location.Allow = cfg.Allow
location.Deny = cfg.Deny
location.PoliciesErrorReturn = cfg.ErrorReturn
}

func addPoliciesCfgToLocations(cfg policiesCfg, locations []version2.Location) {
for i := range locations {
addPoliciesCfgToLocation(cfg, &locations[i])
}
}

func (vsc *virtualServerConfigurator) generateUpstream(owner runtime.Object, upstreamName string, upstream conf_v1.Upstream, isExternalNameSvc bool, endpoints []string) version2.Upstream {
var upsServers []version2.UpstreamServer
for _, e := range endpoints {
Expand Down
32 changes: 32 additions & 0 deletions internal/configs/virtualserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1827,6 +1827,38 @@ func TestGeneratePoliciesFails(t *testing.T) {
}
}

func TestAddPoliciesCfgToLocations(t *testing.T) {
cfg := policiesCfg{
Allow: []string{"127.0.0.1"},
Deny: []string{"127.0.0.2"},
ErrorReturn: &version2.Return{
Code: 400,
},
}

locations := []version2.Location{
{
Path: "/",
},
}

expectedLocations := []version2.Location{
{
Path: "/",
Allow: []string{"127.0.0.1"},
Deny: []string{"127.0.0.2"},
PoliciesErrorReturn: &version2.Return{
Code: 400,
},
},
}

addPoliciesCfgToLocations(cfg, locations)
if !reflect.DeepEqual(locations, expectedLocations) {
t.Errorf("addPoliciesCfgToLocations() returned \n%+v but expected \n%+v", locations, expectedLocations)
}
}

func TestGenerateUpstream(t *testing.T) {
name := "test-upstream"
upstream := conf_v1.Upstream{Service: name, Port: 80}
Expand Down
Loading