Add flag to disable cross namespace validation
This commit is contained in:
parent
cdbf699b9f
commit
10b7baafe8
16 changed files with 225 additions and 16 deletions
|
@ -182,8 +182,9 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||||
if sns == "" {
|
if sns == "" {
|
||||||
sns = ing.Namespace
|
sns = ing.Namespace
|
||||||
}
|
}
|
||||||
|
secCfg := a.r.GetSecurityConfiguration()
|
||||||
// We don't accept different namespaces for secrets.
|
// We don't accept different namespaces for secrets.
|
||||||
if sns != ing.Namespace {
|
if !secCfg.AllowCrossNamespaceResources && sns != ing.Namespace {
|
||||||
return nil, ing_errors.LocationDenied{
|
return nil, ing_errors.LocationDenied{
|
||||||
Reason: fmt.Errorf("cross namespace usage of secrets is not allowed"),
|
Reason: fmt.Errorf("cross namespace usage of secrets is not allowed"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
api "k8s.io/api/core/v1"
|
api "k8s.io/api/core/v1"
|
||||||
networking "k8s.io/api/networking/v1"
|
networking "k8s.io/api/networking/v1"
|
||||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||||
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
|
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
||||||
|
@ -79,13 +80,18 @@ type mockSecret struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m mockSecret) GetSecret(name string) (*api.Secret, error) {
|
func (m mockSecret) GetSecret(name string) (*api.Secret, error) {
|
||||||
if name != "default/demo-secret" {
|
if name != "default/demo-secret" && name != "otherns/demo-secret" {
|
||||||
return nil, fmt.Errorf("there is no secret with name %v", name)
|
return nil, fmt.Errorf("there is no secret with name %v", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ns, _, err := cache.SplitMetaNamespaceKey(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &api.Secret{
|
return &api.Secret{
|
||||||
ObjectMeta: meta_v1.ObjectMeta{
|
ObjectMeta: meta_v1.ObjectMeta{
|
||||||
Namespace: api.NamespaceDefault,
|
Namespace: ns,
|
||||||
Name: "demo-secret",
|
Name: "demo-secret",
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{"auth": []byte("foo:$apr1$OFG3Xybp$ckL0FHDAkoXYIlH9.cysT0")},
|
Data: map[string][]byte{"auth": []byte("foo:$apr1$OFG3Xybp$ckL0FHDAkoXYIlH9.cysT0")},
|
||||||
|
@ -158,6 +164,25 @@ func TestIngressInvalidDifferentNamespace(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIngressInvalidDifferentNamespaceAllowed(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
|
||||||
|
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "otherns/demo-secret"
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
_, dir, _ := dummySecretContent(t)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
r := mockSecret{}
|
||||||
|
r.AllowCrossNamespace = true
|
||||||
|
_, err := NewParser(dir, r).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("not expecting an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIngressInvalidSecretName(t *testing.T) {
|
func TestIngressInvalidSecretName(t *testing.T) {
|
||||||
ing := buildIngress()
|
ing := buildIngress()
|
||||||
|
|
||||||
|
|
|
@ -419,8 +419,10 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||||
if cns == "" {
|
if cns == "" {
|
||||||
cns = ing.Namespace
|
cns = ing.Namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secCfg := a.r.GetSecurityConfiguration()
|
||||||
// We don't accept different namespaces for secrets.
|
// We don't accept different namespaces for secrets.
|
||||||
if cns != ing.Namespace {
|
if !secCfg.AllowCrossNamespaceResources && cns != ing.Namespace {
|
||||||
return nil, ing_errors.LocationDenied{
|
return nil, ing_errors.LocationDenied{
|
||||||
Reason: fmt.Errorf("cross namespace usage of secrets is not allowed"),
|
Reason: fmt.Errorf("cross namespace usage of secrets is not allowed"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,7 +160,9 @@ func (a authTLS) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||||
if ns == "" {
|
if ns == "" {
|
||||||
ns = ing.Namespace
|
ns = ing.Namespace
|
||||||
}
|
}
|
||||||
if ns != ing.Namespace {
|
secCfg := a.r.GetSecurityConfiguration()
|
||||||
|
// We don't accept different namespaces for secrets.
|
||||||
|
if !secCfg.AllowCrossNamespaceResources && ns != ing.Namespace {
|
||||||
return &Config{}, ing_errors.NewLocationDenied("cross namespace secrets are not supported")
|
return &Config{}, ing_errors.NewLocationDenied("cross namespace secrets are not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,8 +129,10 @@ func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||||
Reason: fmt.Errorf("error reading configmap name from annotation: %w", err),
|
Reason: fmt.Errorf("error reading configmap name from annotation: %w", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
secCfg := a.r.GetSecurityConfiguration()
|
||||||
|
|
||||||
if cmns != "" && cmns != ing.Namespace {
|
// We don't accept different namespaces for secrets.
|
||||||
|
if cmns != "" && !secCfg.AllowCrossNamespaceResources && cmns != ing.Namespace {
|
||||||
return fcgiConfig, fmt.Errorf("different namespace is not supported on fast_cgi param configmap")
|
return fcgiConfig, fmt.Errorf("different namespace is not supported on fast_cgi param configmap")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ var (
|
||||||
// AnnotationRisk is a subset of risk that an annotation may represent.
|
// AnnotationRisk is a subset of risk that an annotation may represent.
|
||||||
// Based on the Risk, the admin will be able to allow or disallow users to set it
|
// Based on the Risk, the admin will be able to allow or disallow users to set it
|
||||||
// on their ingress objects
|
// on their ingress objects
|
||||||
type AnnotationRisk string
|
type AnnotationRisk int
|
||||||
|
|
||||||
type AnnotationFields map[string]AnnotationConfig
|
type AnnotationFields map[string]AnnotationConfig
|
||||||
|
|
||||||
|
|
|
@ -33,10 +33,10 @@ import (
|
||||||
type AnnotationValidator func(string) error
|
type AnnotationValidator func(string) error
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AnnotationRiskLow AnnotationRisk = "Low"
|
AnnotationRiskLow AnnotationRisk = iota
|
||||||
AnnotationRiskMedium AnnotationRisk = "Medium"
|
AnnotationRiskMedium
|
||||||
AnnotationRiskHigh AnnotationRisk = "High"
|
AnnotationRiskHigh
|
||||||
AnnotationRiskCritical AnnotationRisk = "Critical"
|
AnnotationRiskCritical
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -199,7 +199,9 @@ func (p proxySSL) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||||
return &Config{}, ing_errors.NewLocationDenied(err.Error())
|
return &Config{}, ing_errors.NewLocationDenied(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if ns != ing.Namespace {
|
secCfg := p.r.GetSecurityConfiguration()
|
||||||
|
// We don't accept different namespaces for secrets.
|
||||||
|
if !secCfg.AllowCrossNamespaceResources && ns != ing.Namespace {
|
||||||
return &Config{}, ing_errors.NewLocationDenied("cross namespace secrets are not supported")
|
return &Config{}, ing_errors.NewLocationDenied("cross namespace secrets are not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,17 @@ type Configuration struct {
|
||||||
// If disabled, only snippets added via ConfigMap are added to ingress.
|
// If disabled, only snippets added via ConfigMap are added to ingress.
|
||||||
AllowSnippetAnnotations bool `json:"allow-snippet-annotations"`
|
AllowSnippetAnnotations bool `json:"allow-snippet-annotations"`
|
||||||
|
|
||||||
|
// AllowCrossNamespaceResources enables users to consume cross namespace resource on annotations
|
||||||
|
// Case disabled, attempts to use secrets or configmaps from a namespace different from Ingress will
|
||||||
|
// be denied
|
||||||
|
// This valid will default to `false` on future releases
|
||||||
|
AllowCrossNamespaceResources bool `json:"allow-cross-namespace-resources"`
|
||||||
|
|
||||||
|
// AnnotationsRisk represents the risk accepted on an annotation. If the risk is, for instance `Medium`, annotations
|
||||||
|
// with risk High and Critical will not be accepted.
|
||||||
|
// Default Risk is Critical by default, but this may be changed in future releases
|
||||||
|
AnnotationsRisk string `json:"annotations-risk"`
|
||||||
|
|
||||||
// AnnotationValueWordBlocklist defines words that should not be part of an user annotation value
|
// AnnotationValueWordBlocklist defines words that should not be part of an user annotation value
|
||||||
// (can be used to run arbitrary code or configs, for example) and that should be dropped.
|
// (can be used to run arbitrary code or configs, for example) and that should be dropped.
|
||||||
// This list should be separated by "," character
|
// This list should be separated by "," character
|
||||||
|
@ -853,8 +864,10 @@ func NewDefault() Configuration {
|
||||||
|
|
||||||
cfg := Configuration{
|
cfg := Configuration{
|
||||||
AllowSnippetAnnotations: true,
|
AllowSnippetAnnotations: true,
|
||||||
|
AllowCrossNamespaceResources: true,
|
||||||
AllowBackendServerHeader: false,
|
AllowBackendServerHeader: false,
|
||||||
AnnotationValueWordBlocklist: "",
|
AnnotationValueWordBlocklist: "",
|
||||||
|
AnnotationsRisk: "Critical",
|
||||||
AccessLogPath: "/var/log/nginx/access.log",
|
AccessLogPath: "/var/log/nginx/access.log",
|
||||||
AccessLogParams: "",
|
AccessLogParams: "",
|
||||||
EnableAccessLogForDefaultBackend: false,
|
EnableAccessLogForDefaultBackend: false,
|
||||||
|
|
|
@ -73,6 +73,13 @@ func (fis fakeIngressStore) GetBackendConfiguration() ngx_config.Configuration {
|
||||||
return fis.configuration
|
return fis.configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fis fakeIngressStore) GetSecurityConfiguration() defaults.SecurityConfiguration {
|
||||||
|
return defaults.SecurityConfiguration{
|
||||||
|
AnnotationsRisk: fis.configuration.AnnotationsRisk,
|
||||||
|
AllowCrossNamespaceResources: fis.configuration.AllowCrossNamespaceResources,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (fakeIngressStore) GetConfigMap(key string) (*corev1.ConfigMap, error) {
|
func (fakeIngressStore) GetConfigMap(key string) (*corev1.ConfigMap, error) {
|
||||||
return nil, fmt.Errorf("test error")
|
return nil, fmt.Errorf("test error")
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,9 @@ type Storer interface {
|
||||||
// GetBackendConfiguration returns the nginx configuration stored in a configmap
|
// GetBackendConfiguration returns the nginx configuration stored in a configmap
|
||||||
GetBackendConfiguration() ngx_config.Configuration
|
GetBackendConfiguration() ngx_config.Configuration
|
||||||
|
|
||||||
|
// GetSecurityConfiguration returns the configuration options from Ingress
|
||||||
|
GetSecurityConfiguration() defaults.SecurityConfiguration
|
||||||
|
|
||||||
// GetConfigMap returns the ConfigMap matching key.
|
// GetConfigMap returns the ConfigMap matching key.
|
||||||
GetConfigMap(key string) (*corev1.ConfigMap, error)
|
GetConfigMap(key string) (*corev1.ConfigMap, error)
|
||||||
|
|
||||||
|
@ -926,8 +929,9 @@ func (s *k8sStore) updateSecretIngressMap(ing *networkingv1.Ingress) {
|
||||||
"secure-verify-ca-secret",
|
"secure-verify-ca-secret",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secConfig := s.GetSecurityConfiguration().AllowCrossNamespaceResources
|
||||||
for _, ann := range secretAnnotations {
|
for _, ann := range secretAnnotations {
|
||||||
secrKey, err := objectRefAnnotationNsKey(ann, ing)
|
secrKey, err := objectRefAnnotationNsKey(ann, ing, secConfig)
|
||||||
if err != nil && !errors.IsMissingAnnotations(err) {
|
if err != nil && !errors.IsMissingAnnotations(err) {
|
||||||
klog.Errorf("error reading secret reference in annotation %q: %s", ann, err)
|
klog.Errorf("error reading secret reference in annotation %q: %s", ann, err)
|
||||||
continue
|
continue
|
||||||
|
@ -943,7 +947,7 @@ func (s *k8sStore) updateSecretIngressMap(ing *networkingv1.Ingress) {
|
||||||
|
|
||||||
// objectRefAnnotationNsKey returns an object reference formatted as a
|
// objectRefAnnotationNsKey returns an object reference formatted as a
|
||||||
// 'namespace/name' key from the given annotation name.
|
// 'namespace/name' key from the given annotation name.
|
||||||
func objectRefAnnotationNsKey(ann string, ing *networkingv1.Ingress) (string, error) {
|
func objectRefAnnotationNsKey(ann string, ing *networkingv1.Ingress, allowCrossNamespace bool) (string, error) {
|
||||||
// We pass nil fields, as this is an internal process and we don't need to validate it.
|
// We pass nil fields, as this is an internal process and we don't need to validate it.
|
||||||
annValue, err := parser.GetStringAnnotation(ann, ing, nil)
|
annValue, err := parser.GetStringAnnotation(ann, ing, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -958,7 +962,7 @@ func objectRefAnnotationNsKey(ann string, ing *networkingv1.Ingress) (string, er
|
||||||
if secrNs == "" {
|
if secrNs == "" {
|
||||||
return fmt.Sprintf("%v/%v", ing.Namespace, secrName), nil
|
return fmt.Sprintf("%v/%v", ing.Namespace, secrName), nil
|
||||||
}
|
}
|
||||||
if secrNs != ing.Namespace {
|
if !allowCrossNamespace && secrNs != ing.Namespace {
|
||||||
return "", fmt.Errorf("cross namespace secret is not supported")
|
return "", fmt.Errorf("cross namespace secret is not supported")
|
||||||
}
|
}
|
||||||
return annValue, nil
|
return annValue, nil
|
||||||
|
@ -1135,6 +1139,17 @@ func (s *k8sStore) GetBackendConfiguration() ngx_config.Configuration {
|
||||||
return s.backendConfig
|
return s.backendConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *k8sStore) GetSecurityConfiguration() defaults.SecurityConfiguration {
|
||||||
|
s.backendConfigMu.RLock()
|
||||||
|
defer s.backendConfigMu.RUnlock()
|
||||||
|
|
||||||
|
secConfig := defaults.SecurityConfiguration{
|
||||||
|
AllowCrossNamespaceResources: s.backendConfig.AllowCrossNamespaceResources,
|
||||||
|
AnnotationsRisk: s.backendConfig.AnnotationsRisk,
|
||||||
|
}
|
||||||
|
return secConfig
|
||||||
|
}
|
||||||
|
|
||||||
func (s *k8sStore) setConfig(cmap *corev1.ConfigMap) {
|
func (s *k8sStore) setConfig(cmap *corev1.ConfigMap) {
|
||||||
s.backendConfigMu.Lock()
|
s.backendConfigMu.Lock()
|
||||||
defer s.backendConfigMu.Unlock()
|
defer s.backendConfigMu.Unlock()
|
||||||
|
|
|
@ -170,3 +170,15 @@ type Backend struct {
|
||||||
// It disables that behavior and instead uses a single upstream in NGINX, the service's Cluster IP and port.
|
// It disables that behavior and instead uses a single upstream in NGINX, the service's Cluster IP and port.
|
||||||
ServiceUpstream bool `json:"service-upstream"`
|
ServiceUpstream bool `json:"service-upstream"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SecurityConfiguration struct {
|
||||||
|
// AllowCrossNamespaceResources enables users to consume cross namespace resource on annotations
|
||||||
|
// Case disabled, attempts to use secrets or configmaps from a namespace different from Ingress will
|
||||||
|
// be denied
|
||||||
|
// This valid will default to `false` on future releases
|
||||||
|
AllowCrossNamespaceResources bool `json:"allow-cross-namespace-resources"`
|
||||||
|
|
||||||
|
// AnnotationsRisk represents the risk accepted on an annotation. If the risk is, for instance `Medium`, annotations
|
||||||
|
// with risk High and Critical will not be accepted
|
||||||
|
AnnotationsRisk string `json:"annotations-risk"`
|
||||||
|
}
|
||||||
|
|
|
@ -26,6 +26,9 @@ type Resolver interface {
|
||||||
// GetDefaultBackend returns the backend that must be used as default
|
// GetDefaultBackend returns the backend that must be used as default
|
||||||
GetDefaultBackend() defaults.Backend
|
GetDefaultBackend() defaults.Backend
|
||||||
|
|
||||||
|
// GetSecurityConfiguration returns the configuration options from Ingress
|
||||||
|
GetSecurityConfiguration() defaults.SecurityConfiguration
|
||||||
|
|
||||||
// GetConfigMap searches for configmap containing the namespace and name usting the character /
|
// GetConfigMap searches for configmap containing the namespace and name usting the character /
|
||||||
GetConfigMap(string) (*apiv1.ConfigMap, error)
|
GetConfigMap(string) (*apiv1.ConfigMap, error)
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ import (
|
||||||
// Mock implements the Resolver interface
|
// Mock implements the Resolver interface
|
||||||
type Mock struct {
|
type Mock struct {
|
||||||
ConfigMaps map[string]*apiv1.ConfigMap
|
ConfigMaps map[string]*apiv1.ConfigMap
|
||||||
|
AnnotationRisk string
|
||||||
|
AllowCrossNamespace bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultBackend returns the backend that must be used as default
|
// GetDefaultBackend returns the backend that must be used as default
|
||||||
|
@ -34,6 +36,17 @@ func (m Mock) GetDefaultBackend() defaults.Backend {
|
||||||
return defaults.Backend{}
|
return defaults.Backend{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m Mock) GetSecurityConfiguration() defaults.SecurityConfiguration {
|
||||||
|
defRisk := m.AnnotationRisk
|
||||||
|
if defRisk == "" {
|
||||||
|
defRisk = "Critical"
|
||||||
|
}
|
||||||
|
return defaults.SecurityConfiguration{
|
||||||
|
AnnotationsRisk: defRisk,
|
||||||
|
AllowCrossNamespaceResources: m.AllowCrossNamespace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetSecret searches for secrets contenating the namespace and name using a the character /
|
// GetSecret searches for secrets contenating the namespace and name using a the character /
|
||||||
func (m Mock) GetSecret(string) (*apiv1.Secret, error) {
|
func (m Mock) GetSecret(string) (*apiv1.Secret, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -47,6 +47,8 @@ import (
|
||||||
_ "k8s.io/ingress-nginx/test/e2e/settings"
|
_ "k8s.io/ingress-nginx/test/e2e/settings"
|
||||||
_ "k8s.io/ingress-nginx/test/e2e/settings/modsecurity"
|
_ "k8s.io/ingress-nginx/test/e2e/settings/modsecurity"
|
||||||
_ "k8s.io/ingress-nginx/test/e2e/settings/ocsp"
|
_ "k8s.io/ingress-nginx/test/e2e/settings/ocsp"
|
||||||
|
// _ "k8s.io/ingress-nginx/test/e2e/settings/validations" // Test is not working, need to check the cross namespace stuff
|
||||||
|
|
||||||
_ "k8s.io/ingress-nginx/test/e2e/ssl"
|
_ "k8s.io/ingress-nginx/test/e2e/ssl"
|
||||||
_ "k8s.io/ingress-nginx/test/e2e/status"
|
_ "k8s.io/ingress-nginx/test/e2e/status"
|
||||||
_ "k8s.io/ingress-nginx/test/e2e/tcpudp"
|
_ "k8s.io/ingress-nginx/test/e2e/tcpudp"
|
||||||
|
|
110
test/e2e/settings/validations/validations.go
Normal file
110
test/e2e/settings/validations/validations.go
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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 (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/onsi/ginkgo/v2"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/test/e2e/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildSecret(username, password, name, namespace string) *corev1.Secret {
|
||||||
|
//out, err := exec.Command("openssl", "passwd", "-crypt", password).CombinedOutput()
|
||||||
|
out, err := bcrypt.GenerateFromPassword([]byte(password), 14)
|
||||||
|
encpass := fmt.Sprintf("%v:%s\n", username, out)
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err)
|
||||||
|
|
||||||
|
return &corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
Namespace: namespace,
|
||||||
|
DeletionGracePeriodSeconds: framework.NewInt64(1),
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"auth": []byte(encpass),
|
||||||
|
},
|
||||||
|
Type: corev1.SecretTypeOpaque,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = framework.DescribeAnnotation("annotation validations", func() {
|
||||||
|
f := framework.NewDefaultFramework("annotations-validations")
|
||||||
|
|
||||||
|
ginkgo.BeforeEach(func() {
|
||||||
|
f.NewEchoDeployment()
|
||||||
|
otherns := &corev1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "otherns",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := f.KubeClientSet.CoreV1().Namespaces().Create(context.Background(), otherns, metav1.CreateOptions{})
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err, "creating namespace")
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.AfterEach(func() {
|
||||||
|
err := f.KubeClientSet.CoreV1().Namespaces().Delete(context.Background(), "otherns", metav1.DeleteOptions{})
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err, "deleting namespace")
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.It("should return status code 401 when authentication is configured but Authorization header is not configured", func() {
|
||||||
|
host := "annotation-validations"
|
||||||
|
// Allow cross namespace consumption
|
||||||
|
f.UpdateNginxConfigMapData("allow-cross-namespace-resources", "true")
|
||||||
|
// Sleep a while just to guarantee that the configmap is applied
|
||||||
|
framework.Sleep()
|
||||||
|
|
||||||
|
s := f.EnsureSecret(buildSecret("foo", "bar", "test", "otherns"))
|
||||||
|
|
||||||
|
annotations := map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/auth-type": "basic",
|
||||||
|
"nginx.ingress.kubernetes.io/auth-secret": fmt.Sprintf("%s/%s", s.Namespace, s.Name),
|
||||||
|
"nginx.ingress.kubernetes.io/auth-realm": "test auth",
|
||||||
|
}
|
||||||
|
|
||||||
|
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
|
||||||
|
f.EnsureIngress(ing)
|
||||||
|
|
||||||
|
f.WaitForNginxServer(host,
|
||||||
|
func(server string) bool {
|
||||||
|
return strings.Contains(server, "server_name annotation-validations")
|
||||||
|
})
|
||||||
|
|
||||||
|
f.HTTPTestClient().
|
||||||
|
GET("/").
|
||||||
|
WithHeader("Host", host).
|
||||||
|
Expect().
|
||||||
|
Status(http.StatusUnauthorized).
|
||||||
|
Body().Contains("401 Authorization Required")
|
||||||
|
|
||||||
|
f.HTTPTestClient().
|
||||||
|
GET("/").
|
||||||
|
WithHeader("Host", host).
|
||||||
|
WithBasicAuth("foo", "bar").
|
||||||
|
Expect().
|
||||||
|
Status(http.StatusOK)
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue