Auth support in frontends for k8s and file
This commit is contained in:
parent
e8e36bd9d5
commit
bb14ec70bd
14 changed files with 867 additions and 181 deletions
|
@ -221,9 +221,9 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
|||
}
|
||||
|
||||
if _, exists := templateObjects.Frontends[baseName]; !exists {
|
||||
basicAuthCreds, err := handleBasicAuthConfig(i, k8sClient)
|
||||
auth, err := getAuthConfig(i, k8sClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to retrieve basic auth configuration for ingress %s/%s: %s", i.Namespace, i.Name, err)
|
||||
log.Errorf("Failed to retrieve auth configuration for ingress %s/%s: %s", i.Namespace, i.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -238,13 +238,13 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
|||
PassTLSCert: passTLSCert,
|
||||
Routes: make(map[string]types.Route),
|
||||
Priority: priority,
|
||||
BasicAuth: basicAuthCreds,
|
||||
WhiteList: getWhiteList(i),
|
||||
Redirect: getFrontendRedirect(i),
|
||||
EntryPoints: entryPoints,
|
||||
Headers: getHeader(i),
|
||||
Errors: getErrorPages(i),
|
||||
RateLimit: getRateLimit(i),
|
||||
Auth: auth,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -438,67 +438,11 @@ func getRuleForHost(host string) string {
|
|||
return "Host:" + host
|
||||
}
|
||||
|
||||
func handleBasicAuthConfig(i *extensionsv1beta1.Ingress, k8sClient Client) ([]string, error) {
|
||||
annotationAuthType := getAnnotationName(i.Annotations, annotationKubernetesAuthType)
|
||||
authType, exists := i.Annotations[annotationAuthType]
|
||||
if !exists {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if strings.ToLower(authType) != "basic" {
|
||||
return nil, fmt.Errorf("unsupported auth-type on annotation ingress.kubernetes.io/auth-type: %q", authType)
|
||||
}
|
||||
|
||||
authSecret := getStringValue(i.Annotations, annotationKubernetesAuthSecret, "")
|
||||
if authSecret == "" {
|
||||
return nil, errors.New("auth-secret annotation ingress.kubernetes.io/auth-secret must be set")
|
||||
}
|
||||
|
||||
basicAuthCreds, err := loadAuthCredentials(i.Namespace, authSecret, k8sClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load auth credentials: %s", err)
|
||||
}
|
||||
|
||||
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, fmt.Errorf("data for secret %q/%q must not be nil", namespace, secretName)
|
||||
case len(secret.Data) != 1:
|
||||
return nil, fmt.Errorf("found %d elements for secret %q/%q, must be single element exactly", len(secret.Data), namespace, secretName)
|
||||
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)
|
||||
}
|
||||
}
|
||||
if len(creds) == 0 {
|
||||
return nil, fmt.Errorf("secret %q/%q does not contain any credentials", namespace, secretName)
|
||||
}
|
||||
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
func getTLS(ingress *extensionsv1beta1.Ingress, k8sClient Client) ([]*tls.Configuration, error) {
|
||||
var tlsConfigs []*tls.Configuration
|
||||
|
||||
for _, t := range ingress.Spec.TLS {
|
||||
tlsSecret, exists, err := k8sClient.GetSecret(ingress.Namespace, t.SecretName)
|
||||
secret, exists, err := k8sClient.GetSecret(ingress.Namespace, t.SecretName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch secret %s/%s: %v", ingress.Namespace, t.SecretName, err)
|
||||
}
|
||||
|
@ -506,19 +450,9 @@ func getTLS(ingress *extensionsv1beta1.Ingress, k8sClient Client) ([]*tls.Config
|
|||
return nil, fmt.Errorf("secret %s/%s does not exist", ingress.Namespace, t.SecretName)
|
||||
}
|
||||
|
||||
tlsCrtData, tlsCrtExists := tlsSecret.Data["tls.crt"]
|
||||
tlsKeyData, tlsKeyExists := tlsSecret.Data["tls.key"]
|
||||
|
||||
var missingEntries []string
|
||||
if !tlsCrtExists {
|
||||
missingEntries = append(missingEntries, "tls.crt")
|
||||
}
|
||||
if !tlsKeyExists {
|
||||
missingEntries = append(missingEntries, "tls.key")
|
||||
}
|
||||
if len(missingEntries) > 0 {
|
||||
return nil, fmt.Errorf("secret %s/%s is missing the following TLS data entries: %s",
|
||||
ingress.Namespace, t.SecretName, strings.Join(missingEntries, ", "))
|
||||
cert, key, err := getCertificateBlocks(secret, ingress.Namespace, t.SecretName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entryPoints := getSliceStringValue(ingress.Annotations, annotationKubernetesFrontendEntryPoints)
|
||||
|
@ -526,8 +460,8 @@ func getTLS(ingress *extensionsv1beta1.Ingress, k8sClient Client) ([]*tls.Config
|
|||
tlsConfig := &tls.Configuration{
|
||||
EntryPoints: entryPoints,
|
||||
Certificate: &tls.Certificate{
|
||||
CertFile: tls.FileOrContent(tlsCrtData),
|
||||
KeyFile: tls.FileOrContent(tlsKeyData),
|
||||
CertFile: tls.FileOrContent(cert),
|
||||
KeyFile: tls.FileOrContent(key),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -537,6 +471,42 @@ func getTLS(ingress *extensionsv1beta1.Ingress, k8sClient Client) ([]*tls.Config
|
|||
return tlsConfigs, nil
|
||||
}
|
||||
|
||||
func getCertificateBlocks(secret *corev1.Secret, namespace, secretName string) (string, string, error) {
|
||||
var missingEntries []string
|
||||
|
||||
tlsCrtData, tlsCrtExists := secret.Data["tls.crt"]
|
||||
if !tlsCrtExists {
|
||||
missingEntries = append(missingEntries, "tls.crt")
|
||||
}
|
||||
|
||||
tlsKeyData, tlsKeyExists := secret.Data["tls.key"]
|
||||
if !tlsKeyExists {
|
||||
missingEntries = append(missingEntries, "tls.key")
|
||||
}
|
||||
|
||||
if len(missingEntries) > 0 {
|
||||
return "", "", fmt.Errorf("secret %s/%s is missing the following TLS data entries: %s",
|
||||
namespace, secretName, strings.Join(missingEntries, ", "))
|
||||
}
|
||||
|
||||
cert := string(tlsCrtData[:])
|
||||
if cert == "" {
|
||||
missingEntries = append(missingEntries, "tls.crt")
|
||||
}
|
||||
|
||||
key := string(tlsKeyData[:])
|
||||
if key == "" {
|
||||
missingEntries = append(missingEntries, "tls.key")
|
||||
}
|
||||
|
||||
if len(missingEntries) > 0 {
|
||||
return "", "", fmt.Errorf("secret %s/%s contains the following empty TLS data entries: %s",
|
||||
namespace, secretName, strings.Join(missingEntries, ", "))
|
||||
}
|
||||
|
||||
return cert, key, nil
|
||||
}
|
||||
|
||||
// endpointPortNumber returns the port to be used for this endpoint. It is zero
|
||||
// if the endpoint does not match the given service port.
|
||||
func endpointPortNumber(servicePort corev1.ServicePort, endpointPorts []corev1.EndpointPort) int32 {
|
||||
|
@ -573,6 +543,160 @@ func (p *Provider) shouldProcessIngress(annotationIngressClass string) bool {
|
|||
return annotationIngressClass == p.IngressClass
|
||||
}
|
||||
|
||||
func getAuthConfig(i *extensionsv1beta1.Ingress, k8sClient Client) (*types.Auth, error) {
|
||||
authType := getStringValue(i.Annotations, annotationKubernetesAuthType, "")
|
||||
if len(authType) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
auth := &types.Auth{
|
||||
HeaderField: getStringValue(i.Annotations, annotationKubernetesAuthHeaderField, ""),
|
||||
}
|
||||
|
||||
switch strings.ToLower(authType) {
|
||||
case "basic":
|
||||
basic, err := getBasicAuthConfig(i, k8sClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth.Basic = basic
|
||||
case "digest":
|
||||
digest, err := getDigestAuthConfig(i, k8sClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth.Digest = digest
|
||||
case "forward":
|
||||
forward, err := getForwardAuthConfig(i, k8sClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
auth.Forward = forward
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported auth-type on annotation %s: %s", annotationKubernetesAuthType, authType)
|
||||
}
|
||||
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
func getBasicAuthConfig(i *extensionsv1beta1.Ingress, k8sClient Client) (*types.Basic, error) {
|
||||
credentials, err := getAuthCredentials(i, k8sClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.Basic{Users: credentials}, nil
|
||||
}
|
||||
|
||||
func getDigestAuthConfig(i *extensionsv1beta1.Ingress, k8sClient Client) (*types.Digest, error) {
|
||||
credentials, err := getAuthCredentials(i, k8sClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.Digest{Users: credentials}, nil
|
||||
}
|
||||
|
||||
func getAuthCredentials(i *extensionsv1beta1.Ingress, k8sClient Client) ([]string, error) {
|
||||
authSecret := getStringValue(i.Annotations, annotationKubernetesAuthSecret, "")
|
||||
if authSecret == "" {
|
||||
return nil, fmt.Errorf("auth-secret annotation %s must be set", annotationKubernetesAuthSecret)
|
||||
}
|
||||
|
||||
auth, err := loadAuthCredentials(i.Namespace, authSecret, k8sClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load auth credentials: %s", err)
|
||||
}
|
||||
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
func loadAuthCredentials(namespace, secretName string, k8sClient Client) ([]string, error) {
|
||||
secret, ok, err := k8sClient.GetSecret(namespace, secretName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch secret %q/%q: %s", namespace, secretName, err)
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("secret %q/%q not found", namespace, secretName)
|
||||
}
|
||||
if secret == nil {
|
||||
return nil, fmt.Errorf("data for secret %q/%q must not be nil", namespace, secretName)
|
||||
}
|
||||
if len(secret.Data) != 1 {
|
||||
return nil, fmt.Errorf("found %d elements for secret %q/%q, must be single element exactly", len(secret.Data), namespace, secretName)
|
||||
}
|
||||
|
||||
var firstSecret []byte
|
||||
for _, v := range secret.Data {
|
||||
firstSecret = v
|
||||
break
|
||||
}
|
||||
|
||||
var credentials []string
|
||||
scanner := bufio.NewScanner(bytes.NewReader(firstSecret))
|
||||
for scanner.Scan() {
|
||||
if cred := scanner.Text(); len(cred) > 0 {
|
||||
credentials = append(credentials, cred)
|
||||
}
|
||||
}
|
||||
|
||||
if len(credentials) == 0 {
|
||||
return nil, fmt.Errorf("secret %q/%q does not contain any credentials", namespace, secretName)
|
||||
}
|
||||
|
||||
return credentials, nil
|
||||
}
|
||||
|
||||
func getForwardAuthConfig(i *extensionsv1beta1.Ingress, k8sClient Client) (*types.Forward, error) {
|
||||
authURL := getStringValue(i.Annotations, annotationKubernetesAuthForwardURL, "")
|
||||
if len(authURL) == 0 {
|
||||
return nil, fmt.Errorf("forward authentication requires a url")
|
||||
}
|
||||
|
||||
forwardAuth := &types.Forward{
|
||||
Address: authURL,
|
||||
TrustForwardHeader: getBoolValue(i.Annotations, annotationKubernetesAuthForwardTrustHeaders, false),
|
||||
AuthResponseHeaders: getSliceStringValue(i.Annotations, annotationKubernetesAuthForwardResponseHeaders),
|
||||
}
|
||||
|
||||
authSecretName := getStringValue(i.Annotations, annotationKubernetesAuthForwardTLSSecret, "")
|
||||
if len(authSecretName) > 0 {
|
||||
authSecretCert, authSecretKey, err := loadAuthTLSSecret(i.Namespace, authSecretName, k8sClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load auth secret: %s", err)
|
||||
}
|
||||
|
||||
forwardAuth.TLS = &types.ClientTLS{
|
||||
Cert: authSecretCert,
|
||||
Key: authSecretKey,
|
||||
InsecureSkipVerify: getBoolValue(i.Annotations, annotationKubernetesAuthForwardTLSInsecure, false),
|
||||
}
|
||||
}
|
||||
|
||||
return forwardAuth, nil
|
||||
}
|
||||
|
||||
func loadAuthTLSSecret(namespace, secretName string, k8sClient Client) (string, string, error) {
|
||||
secret, exists, err := k8sClient.GetSecret(namespace, secretName)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to fetch secret %q/%q: %s", namespace, secretName, err)
|
||||
}
|
||||
if !exists {
|
||||
return "", "", fmt.Errorf("secret %q/%q does not exist", namespace, secretName)
|
||||
}
|
||||
if secret == nil {
|
||||
return "", "", fmt.Errorf("data for secret %q/%q must not be nil", namespace, secretName)
|
||||
}
|
||||
if len(secret.Data) != 2 {
|
||||
return "", "", fmt.Errorf("found %d elements for secret %q/%q, must be two elements exactly", len(secret.Data), namespace, secretName)
|
||||
}
|
||||
|
||||
return getCertificateBlocks(secret, namespace, secretName)
|
||||
}
|
||||
|
||||
func getFrontendRedirect(i *extensionsv1beta1.Ingress) *types.Redirect {
|
||||
permanent := getBoolValue(i.Annotations, annotationKubernetesRedirectPermanent, false)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue