Merge pull request #3756 from anthonyho007/master

Create custom annotation for satisfy "value"
This commit is contained in:
Kubernetes Prow Robot 2019-02-19 13:55:50 -08:00 committed by GitHub
commit 43cfbb91b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 249 additions and 0 deletions

View file

@ -64,6 +64,7 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz
|[nginx.ingress.kubernetes.io/proxy-redirect-to](#proxy-redirect)|string| |[nginx.ingress.kubernetes.io/proxy-redirect-to](#proxy-redirect)|string|
|[nginx.ingress.kubernetes.io/enable-rewrite-log](#enable-rewrite-log)|"true" or "false"| |[nginx.ingress.kubernetes.io/enable-rewrite-log](#enable-rewrite-log)|"true" or "false"|
|[nginx.ingress.kubernetes.io/rewrite-target](#rewrite)|URI| |[nginx.ingress.kubernetes.io/rewrite-target](#rewrite)|URI|
|[nginx.ingress.kubernetes.io/satisfy](#satisfy)|string|
|[nginx.ingress.kubernetes.io/secure-verify-ca-secret](#secure-backends)|string| |[nginx.ingress.kubernetes.io/secure-verify-ca-secret](#secure-backends)|string|
|[nginx.ingress.kubernetes.io/server-alias](#server-alias)|string| |[nginx.ingress.kubernetes.io/server-alias](#server-alias)|string|
|[nginx.ingress.kubernetes.io/server-snippet](#server-snippet)|string| |[nginx.ingress.kubernetes.io/server-snippet](#server-snippet)|string|
@ -760,3 +761,11 @@ When this annotation is set to `true`, the case insensitive regular expression [
Additionally, if the [`rewrite-target` annotation](#rewrite) is used on any Ingress for a given host, then the case insensitive regular expression [location modifier](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) will be enforced on ALL paths for a given host regardless of what Ingress they are defined on. Additionally, if the [`rewrite-target` annotation](#rewrite) is used on any Ingress for a given host, then the case insensitive regular expression [location modifier](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) will be enforced on ALL paths for a given host regardless of what Ingress they are defined on.
Please read about [ingress path matching](../ingress-path-matching.md) before using this modifier. Please read about [ingress path matching](../ingress-path-matching.md) before using this modifier.
### Satisfy
By default, a request would need to satisfy all authentication requirements in order to be allowed. By using this annotation, requests that satisfy either any or all authentication requirements are allowed, based on the configuration value.
```yaml
nginx.ingress.kubernetes.io/satisfy: "any"
```

View file

@ -49,6 +49,7 @@ import (
"k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit" "k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit"
"k8s.io/ingress-nginx/internal/ingress/annotations/redirect" "k8s.io/ingress-nginx/internal/ingress/annotations/redirect"
"k8s.io/ingress-nginx/internal/ingress/annotations/rewrite" "k8s.io/ingress-nginx/internal/ingress/annotations/rewrite"
"k8s.io/ingress-nginx/internal/ingress/annotations/satisfy"
"k8s.io/ingress-nginx/internal/ingress/annotations/secureupstream" "k8s.io/ingress-nginx/internal/ingress/annotations/secureupstream"
"k8s.io/ingress-nginx/internal/ingress/annotations/serversnippet" "k8s.io/ingress-nginx/internal/ingress/annotations/serversnippet"
"k8s.io/ingress-nginx/internal/ingress/annotations/serviceupstream" "k8s.io/ingress-nginx/internal/ingress/annotations/serviceupstream"
@ -86,6 +87,7 @@ type Ingress struct {
RateLimit ratelimit.Config RateLimit ratelimit.Config
Redirect redirect.Config Redirect redirect.Config
Rewrite rewrite.Config Rewrite rewrite.Config
Satisfy string
SecureUpstream secureupstream.Config SecureUpstream secureupstream.Config
ServerSnippet string ServerSnippet string
ServiceUpstream bool ServiceUpstream bool
@ -129,6 +131,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
"RateLimit": ratelimit.NewParser(cfg), "RateLimit": ratelimit.NewParser(cfg),
"Redirect": redirect.NewParser(cfg), "Redirect": redirect.NewParser(cfg),
"Rewrite": rewrite.NewParser(cfg), "Rewrite": rewrite.NewParser(cfg),
"Satisfy": satisfy.NewParser(cfg),
"SecureUpstream": secureupstream.NewParser(cfg), "SecureUpstream": secureupstream.NewParser(cfg),
"ServerSnippet": serversnippet.NewParser(cfg), "ServerSnippet": serversnippet.NewParser(cfg),
"ServiceUpstream": serviceupstream.NewParser(cfg), "ServiceUpstream": serviceupstream.NewParser(cfg),

View file

@ -0,0 +1,43 @@
/*
Copyright 2019 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 satisfy
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
type satisfy struct {
r resolver.Resolver
}
// NewParser creates a new SATISFY annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return satisfy{r}
}
// Parse parses annotation contained in the ingress
func (s satisfy) Parse(ing *extensions.Ingress) (interface{}, error) {
satisfy, err := parser.GetStringAnnotation("satisfy", ing)
if err != nil || satisfy != "any" {
satisfy = "all"
}
return satisfy, nil
}

View file

@ -0,0 +1,95 @@
/*
Copyright 2019 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 satisfy
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "fake",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "fake.host.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/fake",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
func TestSatisfyParser(t *testing.T) {
ing := buildIngress()
data := map[string]string{
"any": "any",
"all": "all",
"invalid": "all",
}
annotations := map[string]string{}
for input, expected := range data {
annotations[parser.GetAnnotationWithPrefix("satisfy")] = input
ing.SetAnnotations(annotations)
satisfyt, err := NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("error parsing annotations: %v", err)
}
val, ok := satisfyt.(string)
if !ok {
t.Errorf("expected a string type but return %t", satisfyt)
}
if val != expected {
t.Errorf("expected %v but returned %v", expected, val)
}
}
}

View file

@ -484,6 +484,7 @@ func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*in
loc.BackendProtocol = anns.BackendProtocol loc.BackendProtocol = anns.BackendProtocol
loc.CustomHTTPErrors = anns.CustomHTTPErrors loc.CustomHTTPErrors = anns.CustomHTTPErrors
loc.ModSecurity = anns.ModSecurity loc.ModSecurity = anns.ModSecurity
loc.Satisfy = anns.Satisfy
if loc.Redirect.FromToWWW { if loc.Redirect.FromToWWW {
server.RedirectFromToWWW = true server.RedirectFromToWWW = true
@ -526,6 +527,7 @@ func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*in
BackendProtocol: anns.BackendProtocol, BackendProtocol: anns.BackendProtocol,
CustomHTTPErrors: anns.CustomHTTPErrors, CustomHTTPErrors: anns.CustomHTTPErrors,
ModSecurity: anns.ModSecurity, ModSecurity: anns.ModSecurity,
Satisfy: anns.Satisfy,
} }
if loc.Redirect.FromToWWW { if loc.Redirect.FromToWWW {

View file

@ -308,6 +308,8 @@ type Location struct {
// ModSecurity allows to enable and configure modsecurity // ModSecurity allows to enable and configure modsecurity
// +optional // +optional
ModSecurity modsecurity.Config `json:"modsecurity"` ModSecurity modsecurity.Config `json:"modsecurity"`
// Satisfy dictates allow access if any or all is set
Satisfy string `json:"satisfy"`
} }
// SSLPassthroughBackend describes a SSL upstream server configured // SSLPassthroughBackend describes a SSL upstream server configured

View file

@ -469,6 +469,10 @@ func (l1 *Location) Equal(l2 *Location) bool {
return false return false
} }
if l1.Satisfy != l2.Satisfy {
return false
}
return true return true
} }

View file

@ -1304,6 +1304,10 @@ stream {
proxy_set_header X-Service-Port $service_port; proxy_set_header X-Service-Port $service_port;
{{ end }} {{ end }}
{{ if $location.Satisfy }}
satisfy {{ $location.Satisfy }};
{{ end }}
{{/* if a location-specific error override is set, add the proxy_intercept here */}} {{/* if a location-specific error override is set, add the proxy_intercept here */}}
{{ if $location.CustomHTTPErrors }} {{ if $location.CustomHTTPErrors }}
# Custom error pages per ingress # Custom error pages per ingress

View file

@ -0,0 +1,87 @@
/*
Copyright 2019 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 annotations
import (
"fmt"
"net/http"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/parnurzeal/gorequest"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/test/e2e/framework"
)
var _ = framework.IngressNginxDescribe("Annotations - SATISFY", func() {
f := framework.NewDefaultFramework("satisfy")
BeforeEach(func() {
f.NewEchoDeployment()
})
AfterEach(func() {
})
It("should configure satisfy directive correctly", func() {
host := "satisfy"
annotationKey := "nginx.ingress.kubernetes.io/satisfy"
annotations := map[string]string{
"any": "any",
"all": "all",
"invalid": "all",
}
results := map[string]string{
"any": "satisfy any",
"all": "satisfy all",
"invalid": "satisfy all",
}
initAnnotations := map[string]string{
annotationKey: "all",
}
ing := framework.NewSingleIngress(host, "/", host, f.IngressController.Namespace, "http-svc", 80, &initAnnotations)
f.EnsureIngress(ing)
for key, result := range results {
err := framework.UpdateIngress(f.KubeClientSet, f.IngressController.Namespace, host, func(ingress *extensions.Ingress) error {
ingress.ObjectMeta.Annotations[annotationKey] = annotations[key]
return nil
})
Expect(err).ToNot(HaveOccurred())
f.WaitForNginxServer(host,
func(server string) bool {
return Expect(server).Should(ContainSubstring(result))
})
resp, body, errs := gorequest.New().
Get(f.IngressController.HTTPURL).
Retry(10, 1*time.Second, http.StatusNotFound).
Set("Host", host).
End()
Expect(errs).Should(BeEmpty())
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
Expect(body).Should(ContainSubstring(fmt.Sprintf("host=%v", host)))
}
})
})