Add annotation to support map of user/pass pairs in basic auth
This commit is contained in:
parent
55820ef1e8
commit
376b862c23
4 changed files with 147 additions and 20 deletions
|
|
@ -20,6 +20,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
api "k8s.io/api/core/v1"
|
||||
|
|
@ -41,12 +42,13 @@ var (
|
|||
|
||||
// Config returns authentication configuration for an Ingress rule
|
||||
type Config struct {
|
||||
Type string `json:"type"`
|
||||
Realm string `json:"realm"`
|
||||
File string `json:"file"`
|
||||
Secured bool `json:"secured"`
|
||||
FileSHA string `json:"fileSha"`
|
||||
Secret string `json:"secret"`
|
||||
Type string `json:"type"`
|
||||
Realm string `json:"realm"`
|
||||
File string `json:"file"`
|
||||
Secured bool `json:"secured"`
|
||||
FileSHA string `json:"fileSha"`
|
||||
Secret string `json:"secret"`
|
||||
SecretType string `json:"secretType"`
|
||||
}
|
||||
|
||||
// Equal tests for equality between two Config types
|
||||
|
|
@ -102,6 +104,12 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
return nil, ing_errors.NewLocationDenied("invalid authentication type")
|
||||
}
|
||||
|
||||
var secretType string
|
||||
secretType, err = parser.GetStringAnnotation("auth-secret-type", ing)
|
||||
if err != nil {
|
||||
secretType = "auth-file"
|
||||
}
|
||||
|
||||
s, err := parser.GetStringAnnotation("auth-secret", ing)
|
||||
if err != nil {
|
||||
return nil, ing_errors.LocationDenied{
|
||||
|
|
@ -131,24 +139,37 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
realm, _ := parser.GetStringAnnotation("auth-realm", ing)
|
||||
|
||||
passFile := fmt.Sprintf("%v/%v-%v.passwd", a.authDirectory, ing.GetNamespace(), ing.GetName())
|
||||
err = dumpSecret(passFile, secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
if secretType == "auth-file" {
|
||||
err = dumpSecretAuthFile(passFile, secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if secretType == "auth-map" {
|
||||
err = dumpSecretAuthMap(passFile, secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, ing_errors.LocationDenied{
|
||||
Reason: errors.Wrap(err, "invalid auth-secret-type in annotation, must be 'auth-file' or 'auth-map'"),
|
||||
}
|
||||
}
|
||||
|
||||
return &Config{
|
||||
Type: at,
|
||||
Realm: realm,
|
||||
File: passFile,
|
||||
Secured: true,
|
||||
FileSHA: file.SHA1(passFile),
|
||||
Secret: name,
|
||||
Type: at,
|
||||
Realm: realm,
|
||||
File: passFile,
|
||||
Secured: true,
|
||||
FileSHA: file.SHA1(passFile),
|
||||
Secret: name,
|
||||
SecretType: secretType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// dumpSecret dumps the content of a secret into a file
|
||||
// in the expected format for the specified authorization
|
||||
func dumpSecret(filename string, secret *api.Secret) error {
|
||||
func dumpSecretAuthFile(filename string, secret *api.Secret) error {
|
||||
val, ok := secret.Data["auth"]
|
||||
if !ok {
|
||||
return ing_errors.LocationDenied{
|
||||
|
|
@ -165,3 +186,22 @@ func dumpSecret(filename string, secret *api.Secret) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func dumpSecretAuthMap(filename string, secret *api.Secret) error {
|
||||
builder := &strings.Builder{}
|
||||
for user, pass := range secret.Data {
|
||||
builder.WriteString(user)
|
||||
builder.WriteString(":")
|
||||
builder.WriteString(string(pass))
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
|
||||
err := ioutil.WriteFile(filename, []byte(builder.String()), file.ReadWriteByUser)
|
||||
if err != nil {
|
||||
return ing_errors.LocationDenied{
|
||||
Reason: errors.Wrap(err, "unexpected error creating password file"),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,6 +182,25 @@ func TestIngressAuthWithoutSecret(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestIngressAuthInvalidSecretKey(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
|
||||
data := map[string]string{}
|
||||
data[parser.GetAnnotationWithPrefix("auth-type")] = "basic"
|
||||
data[parser.GetAnnotationWithPrefix("auth-secret")] = "demo-secret"
|
||||
data[parser.GetAnnotationWithPrefix("auth-secret-type")] = "invalid-type"
|
||||
data[parser.GetAnnotationWithPrefix("auth-realm")] = "-realm-"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
_, dir, _ := dummySecretContent(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
_, err := NewParser(dir, mockSecret{}).Parse(ing)
|
||||
if err == nil {
|
||||
t.Errorf("expected an error with invalid secret name")
|
||||
}
|
||||
}
|
||||
|
||||
func dummySecretContent(t *testing.T) (string, string, *api.Secret) {
|
||||
dir, err := ioutil.TempDir("", fmt.Sprintf("%v", time.Now().Unix()))
|
||||
if err != nil {
|
||||
|
|
@ -197,20 +216,30 @@ func dummySecretContent(t *testing.T) (string, string, *api.Secret) {
|
|||
return tmpfile.Name(), dir, s
|
||||
}
|
||||
|
||||
func TestDumpSecret(t *testing.T) {
|
||||
func TestDumpSecretAuthFile(t *testing.T) {
|
||||
tmpfile, dir, s := dummySecretContent(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
sd := s.Data
|
||||
s.Data = nil
|
||||
|
||||
err := dumpSecret(tmpfile, s)
|
||||
err := dumpSecretAuthFile(tmpfile, s)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error with secret without auth")
|
||||
}
|
||||
|
||||
s.Data = sd
|
||||
err = dumpSecret(tmpfile, s)
|
||||
err = dumpSecretAuthFile(tmpfile, s)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error creating htpasswd file %v: %v", tmpfile, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDumpSecretAuthMap(t *testing.T) {
|
||||
tmpfile, dir, s := dummySecretContent(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
err := dumpSecretAuthMap(tmpfile, s)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error creating htpasswd file %v: %v", tmpfile, err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue