1
0
Fork 0

Auth support in frontends for k8s and file

This commit is contained in:
Mikael Rapp 2018-07-02 11:52:04 +02:00 committed by Traefiker Bot
parent e8e36bd9d5
commit bb14ec70bd
14 changed files with 867 additions and 181 deletions

View file

@ -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)