Merge pull request #1488 from alpe/k8s-auth
Add basic auth to kubernetes provider
This commit is contained in:
commit
b5283391dd
6 changed files with 296 additions and 12 deletions
17
docs/toml.md
17
docs/toml.md
|
@ -1186,6 +1186,22 @@ Additionally, an annotation can be used on Kubernetes services to set the [circu
|
||||||
|
|
||||||
- `traefik.backend.circuitbreaker: <expression>`: set the circuit breaker expression for the backend (Default: nil).
|
- `traefik.backend.circuitbreaker: <expression>`: set the circuit breaker expression for the backend (Default: nil).
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
Is possible to add additional authentication annotations in the Ingress rule.
|
||||||
|
The source of the authentication is a secret that contains usernames and passwords inside the the key auth.
|
||||||
|
|
||||||
|
- `ingress.kubernetes.io/auth-type`: `basic`
|
||||||
|
- `ingress.kubernetes.io/auth-secret`: contains the usernames and passwords with access to the paths defined in the Ingress Rule.
|
||||||
|
|
||||||
|
The secret must be created in the same namespace as the Ingress rule.
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
|
||||||
|
- Basic authentication only.
|
||||||
|
- Realm not configurable; only `traefik` default.
|
||||||
|
- Secret must contain only single file.
|
||||||
|
|
||||||
## Consul backend
|
## Consul backend
|
||||||
|
|
||||||
Træfik can be configured to use Consul as a backend configuration:
|
Træfik can be configured to use Consul as a backend configuration:
|
||||||
|
@ -1719,7 +1735,6 @@ RefreshSeconds = 15
|
||||||
|
|
||||||
Items in the `dynamodb` table must have three attributes:
|
Items in the `dynamodb` table must have three attributes:
|
||||||
|
|
||||||
|
|
||||||
- `id` : string
|
- `id` : string
|
||||||
- The id is the primary key.
|
- The id is the primary key.
|
||||||
- `name` : string
|
- `name` : string
|
||||||
|
|
|
@ -505,19 +505,22 @@ You should now be able to visit the websites in your browser.
|
||||||
* [cheeses.local/wensleydale](http://cheeses.local/wensleydale/)
|
* [cheeses.local/wensleydale](http://cheeses.local/wensleydale/)
|
||||||
|
|
||||||
## Disable passing the Host header
|
## Disable passing the Host header
|
||||||
By default Træfik will pass the incoming Host header on to the upstream resource. There
|
|
||||||
are times however where you may not want this to be the case. For example if your service
|
By default Træfik will pass the incoming Host header on to the upstream resource.
|
||||||
is of the ExternalName type.
|
There are times however where you may not want this to be the case.
|
||||||
|
For example if your service is of the ExternalName type.
|
||||||
|
|
||||||
### Disable entirely
|
### Disable entirely
|
||||||
|
|
||||||
Add the following to your toml config:
|
Add the following to your toml config:
|
||||||
```toml
|
```toml
|
||||||
disablePassHostHeaders = true
|
disablePassHostHeaders = true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Disable per ingress
|
### Disable per ingress
|
||||||
To disable passing the Host header per ingress resource set the "traefik.frontend.passHostHeader"
|
|
||||||
annotation on your ingress to "false".
|
To disable passing the Host header per ingress resource set the `traefik.frontend.passHostHeader`
|
||||||
|
annotation on your ingress to `false`.
|
||||||
|
|
||||||
Here is an example ingress definition:
|
Here is an example ingress definition:
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -557,16 +560,15 @@ If you were to visit example.com/static the request would then be passed onto
|
||||||
static.otherdomain.com/static and static.otherdomain.com would receive the
|
static.otherdomain.com/static and static.otherdomain.com would receive the
|
||||||
request with the Host header being static.otherdomain.com.
|
request with the Host header being static.otherdomain.com.
|
||||||
|
|
||||||
Note: The per ingress annotation overides whatever the global value is set to. So you
|
Note: The per ingress annotation overides whatever the global value is set to.
|
||||||
could set `disablePassHostHeaders` to true in your toml file and then enable passing
|
So you could set `disablePassHostHeaders` to `true` in your toml file and then enable passing
|
||||||
the host header per ingress if you wanted.
|
the host header per ingress if you wanted.
|
||||||
|
|
||||||
## Excluding an ingress from Træfik
|
## Excluding an ingress from Træfik
|
||||||
|
|
||||||
You can control which ingress Træfik cares about by using the `kubernetes.io/ingress.class`
|
You can control which ingress Træfik cares about by using the `kubernetes.io/ingress.class` annotation.
|
||||||
annotation. By default if the annotation is not set at all Træfik will include the
|
By default if the annotation is not set at all Træfik will include the ingress.
|
||||||
ingress. If the annotation is set to anything other than traefik or a blank string
|
If the annotation is set to anything other than traefik or a blank string Træfik will ignore it.
|
||||||
Træfik will ignore it.
|
|
||||||
|
|
||||||
|
|
||||||
![](http://i.giphy.com/ujUdrdpX7Ok5W.gif)
|
![](http://i.giphy.com/ujUdrdpX7Ok5W.gif)
|
||||||
|
|
|
@ -26,6 +26,7 @@ const resyncPeriod = time.Minute * 5
|
||||||
type Client interface {
|
type Client interface {
|
||||||
GetIngresses(namespaces Namespaces) []*v1beta1.Ingress
|
GetIngresses(namespaces Namespaces) []*v1beta1.Ingress
|
||||||
GetService(namespace, name string) (*v1.Service, bool, error)
|
GetService(namespace, name string) (*v1.Service, bool, error)
|
||||||
|
GetSecret(namespace, name string) (*v1.Secret, bool, error)
|
||||||
GetEndpoints(namespace, name string) (*v1.Endpoints, bool, error)
|
GetEndpoints(namespace, name string) (*v1.Endpoints, bool, error)
|
||||||
WatchAll(labelSelector string, stopCh <-chan struct{}) (<-chan interface{}, error)
|
WatchAll(labelSelector string, stopCh <-chan struct{}) (<-chan interface{}, error)
|
||||||
}
|
}
|
||||||
|
@ -34,10 +35,12 @@ type clientImpl struct {
|
||||||
ingController *cache.Controller
|
ingController *cache.Controller
|
||||||
svcController *cache.Controller
|
svcController *cache.Controller
|
||||||
epController *cache.Controller
|
epController *cache.Controller
|
||||||
|
secController *cache.Controller
|
||||||
|
|
||||||
ingStore cache.Store
|
ingStore cache.Store
|
||||||
svcStore cache.Store
|
svcStore cache.Store
|
||||||
epStore cache.Store
|
epStore cache.Store
|
||||||
|
secStore cache.Store
|
||||||
|
|
||||||
clientset *kubernetes.Clientset
|
clientset *kubernetes.Clientset
|
||||||
}
|
}
|
||||||
|
@ -154,6 +157,16 @@ func (c *clientImpl) GetService(namespace, name string) (*v1.Service, bool, erro
|
||||||
return service, exists, err
|
return service, exists, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) GetSecret(namespace, name string) (*v1.Secret, bool, error) {
|
||||||
|
var secret *v1.Secret
|
||||||
|
item, exists, err := c.secStore.GetByKey(namespace + "/" + name)
|
||||||
|
if err == nil && item != nil {
|
||||||
|
secret = item.(*v1.Secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
return secret, exists, err
|
||||||
|
}
|
||||||
|
|
||||||
// WatchServices starts the watch of Provider Service resources and updates the corresponding store
|
// WatchServices starts the watch of Provider Service resources and updates the corresponding store
|
||||||
func (c *clientImpl) WatchServices(watchCh chan<- interface{}, stopCh <-chan struct{}) {
|
func (c *clientImpl) WatchServices(watchCh chan<- interface{}, stopCh <-chan struct{}) {
|
||||||
source := cache.NewListWatchFromClient(
|
source := cache.NewListWatchFromClient(
|
||||||
|
@ -199,6 +212,21 @@ func (c *clientImpl) WatchEndpoints(watchCh chan<- interface{}, stopCh <-chan st
|
||||||
go c.epController.Run(stopCh)
|
go c.epController.Run(stopCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) WatchSecrets(watchCh chan<- interface{}, stopCh <-chan struct{}) {
|
||||||
|
source := cache.NewListWatchFromClient(
|
||||||
|
c.clientset.CoreV1().RESTClient(),
|
||||||
|
"secrets",
|
||||||
|
api.NamespaceAll,
|
||||||
|
fields.Everything())
|
||||||
|
|
||||||
|
c.secStore, c.secController = cache.NewInformer(
|
||||||
|
source,
|
||||||
|
&v1.Endpoints{},
|
||||||
|
resyncPeriod,
|
||||||
|
newResourceEventHandlerFuncs(watchCh))
|
||||||
|
go c.secController.Run(stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
// WatchAll returns events in the cluster and updates the stores via informer
|
// WatchAll returns events in the cluster and updates the stores via informer
|
||||||
// Filters ingresses by labelSelector
|
// Filters ingresses by labelSelector
|
||||||
func (c *clientImpl) WatchAll(labelSelector string, stopCh <-chan struct{}) (<-chan interface{}, error) {
|
func (c *clientImpl) WatchAll(labelSelector string, stopCh <-chan struct{}) (<-chan interface{}, error) {
|
||||||
|
@ -213,6 +241,7 @@ func (c *clientImpl) WatchAll(labelSelector string, stopCh <-chan struct{}) (<-c
|
||||||
c.WatchIngresses(kubeLabelSelector, eventCh, stopCh)
|
c.WatchIngresses(kubeLabelSelector, eventCh, stopCh)
|
||||||
c.WatchServices(eventCh, stopCh)
|
c.WatchServices(eventCh, stopCh)
|
||||||
c.WatchEndpoints(eventCh, stopCh)
|
c.WatchEndpoints(eventCh, stopCh)
|
||||||
|
c.WatchSecrets(eventCh, stopCh)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer close(watchCh)
|
defer close(watchCh)
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package kubernetes
|
package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -16,6 +19,7 @@ import (
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"k8s.io/client-go/pkg/api/v1"
|
"k8s.io/client-go/pkg/api/v1"
|
||||||
|
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||||
"k8s.io/client-go/pkg/util/intstr"
|
"k8s.io/client-go/pkg/util/intstr"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,6 +33,8 @@ const (
|
||||||
ruleTypePathPrefix = "PathPrefix"
|
ruleTypePathPrefix = "PathPrefix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const traefikDefaultRealm = "traefik"
|
||||||
|
|
||||||
// Provider holds configurations of the provider.
|
// Provider holds configurations of the provider.
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
provider.BaseProvider `mapstructure:",squash"`
|
provider.BaseProvider `mapstructure:",squash"`
|
||||||
|
@ -159,13 +165,21 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
||||||
default:
|
default:
|
||||||
log.Warnf("Unknown value of %s for traefik.frontend.passHostHeader, falling back to %s", passHostHeaderAnnotation, PassHostHeader)
|
log.Warnf("Unknown value of %s for traefik.frontend.passHostHeader, falling back to %s", passHostHeaderAnnotation, PassHostHeader)
|
||||||
}
|
}
|
||||||
|
if realm := i.Annotations["ingress.kubernetes.io/auth-realm"]; realm != "" && realm != traefikDefaultRealm {
|
||||||
|
return nil, errors.New("no realm customization supported")
|
||||||
|
}
|
||||||
|
|
||||||
if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists {
|
if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists {
|
||||||
|
basicAuthCreds, err := handleBasicAuthConfig(i, k8sClient)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
templateObjects.Frontends[r.Host+pa.Path] = &types.Frontend{
|
templateObjects.Frontends[r.Host+pa.Path] = &types.Frontend{
|
||||||
Backend: r.Host + pa.Path,
|
Backend: r.Host + pa.Path,
|
||||||
PassHostHeader: PassHostHeader,
|
PassHostHeader: PassHostHeader,
|
||||||
Routes: make(map[string]types.Route),
|
Routes: make(map[string]types.Route),
|
||||||
Priority: len(pa.Path),
|
Priority: len(pa.Path),
|
||||||
|
BasicAuth: basicAuthCreds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(r.Host) > 0 {
|
if len(r.Host) > 0 {
|
||||||
|
@ -278,6 +292,56 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
||||||
return &templateObjects, nil
|
return &templateObjects, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleBasicAuthConfig(i *v1beta1.Ingress, k8sClient Client) ([]string, error) {
|
||||||
|
authType, exists := i.Annotations["ingress.kubernetes.io/auth-type"]
|
||||||
|
if !exists {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if strings.ToLower(authType) != "basic" {
|
||||||
|
return nil, fmt.Errorf("unsupported auth-type: %q", authType)
|
||||||
|
}
|
||||||
|
authSecret := i.Annotations["ingress.kubernetes.io/auth-secret"]
|
||||||
|
if authSecret == "" {
|
||||||
|
return nil, errors.New("auth-secret annotation must be set")
|
||||||
|
}
|
||||||
|
basicAuthCreds, err := loadAuthCredentials(i.Namespace, authSecret, k8sClient)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(basicAuthCreds) == 0 {
|
||||||
|
return nil, errors.New("secret file without credentials")
|
||||||
|
}
|
||||||
|
return basicAuthCreds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadAuthCredentials(namespace, secretName string, k8sClient Client) ([]string, error) {
|
||||||
|
secret, ok, err := k8sClient.GetSecret(namespace, secretName)
|
||||||
|
switch { // keep order of case conditions
|
||||||
|
case err != nil:
|
||||||
|
return nil, fmt.Errorf("failed to fetch secret %q/%q: %s", namespace, secretName, err)
|
||||||
|
case !ok:
|
||||||
|
return nil, fmt.Errorf("secret %q/%q not found", namespace, secretName)
|
||||||
|
case secret == nil:
|
||||||
|
return nil, errors.New("secret data must not be nil")
|
||||||
|
case len(secret.Data) != 1:
|
||||||
|
return nil, errors.New("secret must contain single element only")
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
var firstSecret []byte
|
||||||
|
for _, v := range secret.Data {
|
||||||
|
firstSecret = v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
creds := make([]string, 0)
|
||||||
|
scanner := bufio.NewScanner(bytes.NewReader(firstSecret))
|
||||||
|
for scanner.Scan() {
|
||||||
|
if cred := scanner.Text(); cred != "" {
|
||||||
|
creds = append(creds, cred)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return creds, nil
|
||||||
|
}
|
||||||
|
|
||||||
func endpointPortNumber(servicePort v1.ServicePort, endpointPorts []v1.EndpointPort) int {
|
func endpointPortNumber(servicePort v1.ServicePort, endpointPorts []v1.EndpointPort) int {
|
||||||
if len(endpointPorts) > 0 {
|
if len(endpointPorts) > 0 {
|
||||||
//name is optional if there is only one port
|
//name is optional if there is only one port
|
||||||
|
|
|
@ -1466,6 +1466,35 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: v1.ObjectMeta{
|
||||||
|
Namespace: "testing",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"ingress.kubernetes.io/auth-type": "basic",
|
||||||
|
"ingress.kubernetes.io/auth-secret": "mySecret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: v1beta1.IngressSpec{
|
||||||
|
Rules: []v1beta1.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "basic",
|
||||||
|
IngressRuleValue: v1beta1.IngressRuleValue{
|
||||||
|
HTTP: &v1beta1.HTTPIngressRuleValue{
|
||||||
|
Paths: []v1beta1.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/auth",
|
||||||
|
Backend: v1beta1.IngressBackend{
|
||||||
|
ServiceName: "service1",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ObjectMeta: v1.ObjectMeta{
|
ObjectMeta: v1.ObjectMeta{
|
||||||
Namespace: "testing",
|
Namespace: "testing",
|
||||||
|
@ -1515,12 +1544,25 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
secrets := []*v1.Secret{
|
||||||
|
{
|
||||||
|
ObjectMeta: v1.ObjectMeta{
|
||||||
|
Name: "mySecret",
|
||||||
|
UID: "1",
|
||||||
|
Namespace: "testing",
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"auth": []byte("myUser:myEncodedPW"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
endpoints := []*v1.Endpoints{}
|
endpoints := []*v1.Endpoints{}
|
||||||
watchChan := make(chan interface{})
|
watchChan := make(chan interface{})
|
||||||
client := clientMock{
|
client := clientMock{
|
||||||
ingresses: ingresses,
|
ingresses: ingresses,
|
||||||
services: services,
|
services: services,
|
||||||
|
secrets: secrets,
|
||||||
endpoints: endpoints,
|
endpoints: endpoints,
|
||||||
watchChan: watchChan,
|
watchChan: watchChan,
|
||||||
}
|
}
|
||||||
|
@ -1558,6 +1600,19 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
Method: "wrr",
|
Method: "wrr",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"basic/auth": {
|
||||||
|
Servers: map[string]types.Server{
|
||||||
|
"http://example.com": {
|
||||||
|
URL: "http://example.com",
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CircuitBreaker: nil,
|
||||||
|
LoadBalancer: &types.LoadBalancer{
|
||||||
|
Sticky: false,
|
||||||
|
Method: "wrr",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Frontends: map[string]*types.Frontend{
|
Frontends: map[string]*types.Frontend{
|
||||||
"foo/bar": {
|
"foo/bar": {
|
||||||
|
@ -1586,6 +1641,20 @@ func TestIngressAnnotations(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"basic/auth": {
|
||||||
|
Backend: "basic/auth",
|
||||||
|
PassHostHeader: true,
|
||||||
|
Priority: len("/auth"),
|
||||||
|
Routes: map[string]types.Route{
|
||||||
|
"/auth": {
|
||||||
|
Rule: "PathPrefix:/auth",
|
||||||
|
},
|
||||||
|
"basic": {
|
||||||
|
Rule: "Host:basic",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
BasicAuth: []string{"myUser:myEncodedPW"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2030,13 +2099,102 @@ func TestMissingResources(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBasicAuthInTemplate(t *testing.T) {
|
||||||
|
ingresses := []*v1beta1.Ingress{
|
||||||
|
{
|
||||||
|
ObjectMeta: v1.ObjectMeta{
|
||||||
|
Namespace: "testing",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"ingress.kubernetes.io/auth-type": "basic",
|
||||||
|
"ingress.kubernetes.io/auth-secret": "mySecret",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: v1beta1.IngressSpec{
|
||||||
|
Rules: []v1beta1.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "basic",
|
||||||
|
IngressRuleValue: v1beta1.IngressRuleValue{
|
||||||
|
HTTP: &v1beta1.HTTPIngressRuleValue{
|
||||||
|
Paths: []v1beta1.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/auth",
|
||||||
|
Backend: v1beta1.IngressBackend{
|
||||||
|
ServiceName: "service1",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
services := []*v1.Service{
|
||||||
|
{
|
||||||
|
ObjectMeta: v1.ObjectMeta{
|
||||||
|
Name: "service1",
|
||||||
|
UID: "1",
|
||||||
|
Namespace: "testing",
|
||||||
|
},
|
||||||
|
Spec: v1.ServiceSpec{
|
||||||
|
ClusterIP: "10.0.0.1",
|
||||||
|
Type: "ExternalName",
|
||||||
|
ExternalName: "example.com",
|
||||||
|
Ports: []v1.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "http",
|
||||||
|
Port: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
secrets := []*v1.Secret{
|
||||||
|
{
|
||||||
|
ObjectMeta: v1.ObjectMeta{
|
||||||
|
Name: "mySecret",
|
||||||
|
UID: "1",
|
||||||
|
Namespace: "testing",
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"auth": []byte("myUser:myEncodedPW"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoints := []*v1.Endpoints{}
|
||||||
|
watchChan := make(chan interface{})
|
||||||
|
client := clientMock{
|
||||||
|
ingresses: ingresses,
|
||||||
|
services: services,
|
||||||
|
secrets: secrets,
|
||||||
|
endpoints: endpoints,
|
||||||
|
watchChan: watchChan,
|
||||||
|
}
|
||||||
|
provider := Provider{}
|
||||||
|
actual, err := provider.loadIngresses(client)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual = provider.loadConfig(*actual)
|
||||||
|
got := actual.Frontends["basic/auth"].BasicAuth
|
||||||
|
if !reflect.DeepEqual(got, []string{"myUser:myEncodedPW"}) {
|
||||||
|
t.Fatalf("unexpected credentials: %+v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type clientMock struct {
|
type clientMock struct {
|
||||||
ingresses []*v1beta1.Ingress
|
ingresses []*v1beta1.Ingress
|
||||||
services []*v1.Service
|
services []*v1.Service
|
||||||
|
secrets []*v1.Secret
|
||||||
endpoints []*v1.Endpoints
|
endpoints []*v1.Endpoints
|
||||||
watchChan chan interface{}
|
watchChan chan interface{}
|
||||||
|
|
||||||
apiServiceError error
|
apiServiceError error
|
||||||
|
apiSecretError error
|
||||||
apiEndpointsError error
|
apiEndpointsError error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2064,6 +2222,19 @@ func (c clientMock) GetService(namespace, name string) (*v1.Service, bool, error
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c clientMock) GetSecret(namespace, name string) (*v1.Secret, bool, error) {
|
||||||
|
if c.apiSecretError != nil {
|
||||||
|
return nil, false, c.apiSecretError
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, secret := range c.secrets {
|
||||||
|
if secret.Namespace == namespace && secret.Name == name {
|
||||||
|
return secret, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c clientMock) GetEndpoints(namespace, name string) (*v1.Endpoints, bool, error) {
|
func (c clientMock) GetEndpoints(namespace, name string) (*v1.Endpoints, bool, error) {
|
||||||
if c.apiEndpointsError != nil {
|
if c.apiEndpointsError != nil {
|
||||||
return nil, false, c.apiEndpointsError
|
return nil, false, c.apiEndpointsError
|
||||||
|
|
|
@ -20,6 +20,9 @@
|
||||||
backend = "{{$frontend.Backend}}"
|
backend = "{{$frontend.Backend}}"
|
||||||
priority = {{$frontend.Priority}}
|
priority = {{$frontend.Priority}}
|
||||||
passHostHeader = {{$frontend.PassHostHeader}}
|
passHostHeader = {{$frontend.PassHostHeader}}
|
||||||
|
basicAuth = [{{range $frontend.BasicAuth}}
|
||||||
|
"{{.}}",
|
||||||
|
{{end}}]
|
||||||
{{range $routeName, $route := $frontend.Routes}}
|
{{range $routeName, $route := $frontend.Routes}}
|
||||||
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
|
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
|
||||||
rule = "{{$route.Rule}}"
|
rule = "{{$route.Rule}}"
|
||||||
|
|
Loading…
Reference in a new issue