github.com/kubewharf/katalyst-core@v0.5.3/pkg/util/credential/basic.go (about)

     1  /*
     2  Copyright 2022 The Katalyst Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package credential
    18  
    19  import (
    20  	"context"
    21  	"encoding/base64"
    22  	"fmt"
    23  	"net/http"
    24  	"strings"
    25  	"sync"
    26  	"time"
    27  
    28  	"k8s.io/apimachinery/pkg/util/wait"
    29  
    30  	"github.com/kubewharf/katalyst-core/pkg/config/agent/dynamic"
    31  	"github.com/kubewharf/katalyst-core/pkg/config/generic"
    32  	"github.com/kubewharf/katalyst-core/pkg/util/general"
    33  )
    34  
    35  const (
    36  	secretSyncInterval = 30 * time.Second
    37  )
    38  
    39  type BasicAuthInfo struct {
    40  	Username string
    41  	Password string
    42  }
    43  
    44  func (b BasicAuthInfo) AuthType() AuthType {
    45  	return AuthTypeBasicAuth
    46  }
    47  
    48  func (b BasicAuthInfo) SubjectName() string {
    49  	return b.Username
    50  }
    51  
    52  func NewBasicAuthCredential(_ *generic.AuthConfiguration, dynamicConfiguration *dynamic.DynamicAgentConfiguration) (Credential, error) {
    53  	return &basicAuthCredential{
    54  		authPairs:     map[string]string{},
    55  		dynamicConfig: dynamicConfiguration,
    56  	}, nil
    57  }
    58  
    59  type basicAuthCredential struct {
    60  	mutex         sync.RWMutex
    61  	dynamicConfig *dynamic.DynamicAgentConfiguration
    62  	authPairs     map[string]string
    63  }
    64  
    65  func (b *basicAuthCredential) Run(ctx context.Context) {
    66  	go wait.Until(b.updateAuthPairFromDynamicConf, secretSyncInterval, ctx.Done())
    67  }
    68  
    69  func (b *basicAuthCredential) AuthType() AuthType {
    70  	return AuthTypeBasicAuth
    71  }
    72  
    73  func (b *basicAuthCredential) Auth(r *http.Request) (AuthInfo, error) {
    74  	username, password, ok := r.BasicAuth()
    75  
    76  	if !ok {
    77  		return nil, fmt.Errorf("invalid basic auth token:%v", r.Header.Get("Authorization"))
    78  	}
    79  
    80  	err := b.verifyAuthInfo(username, password)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	return BasicAuthInfo{Username: username, Password: password}, nil
    86  }
    87  
    88  func (b *basicAuthCredential) AuthToken(token string) (AuthInfo, error) {
    89  	username, password, ok := parseBasicAuth(token)
    90  	if !ok {
    91  		return nil, fmt.Errorf("invalid basic auth token:%v", token)
    92  	}
    93  
    94  	err := b.verifyAuthInfo(username, password)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	return BasicAuthInfo{Username: username, Password: password}, nil
   100  }
   101  
   102  func (b *basicAuthCredential) verifyAuthInfo(username, password string) error {
   103  	b.mutex.RLock()
   104  	defer b.mutex.RUnlock()
   105  
   106  	storedPassword, ok := b.authPairs[username]
   107  	if !ok {
   108  		return fmt.Errorf("user %v not found in store", username)
   109  	}
   110  	if storedPassword != password {
   111  		return fmt.Errorf("password for user %v are wrong", username)
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  // parseBasicAuth parses an HTTP Basic Authentication string.
   118  // "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true).
   119  func parseBasicAuth(auth string) (username, password string, ok bool) {
   120  	const prefix = "Basic "
   121  	// Case-insensitive prefix match.
   122  	if len(auth) < len(prefix) || !strings.EqualFold(strings.ToLower(auth[:len(prefix)]), strings.ToLower(prefix)) {
   123  		return "", "", false
   124  	}
   125  	c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
   126  	if err != nil {
   127  		return "", "", false
   128  	}
   129  	cs := string(c)
   130  	username, password, ok = strings.Cut(cs, ":")
   131  	if !ok {
   132  		return "", "", false
   133  	}
   134  	return username, password, true
   135  }
   136  
   137  func (b *basicAuthCredential) updateAuthPairFromDynamicConf() {
   138  	dynamicConfiguration := b.dynamicConfig.GetDynamicConfiguration()
   139  	newAuthPairs := make(map[string]string)
   140  	for _, pair := range dynamicConfiguration.UserPasswordPairs {
   141  		p, err := base64.StdEncoding.DecodeString(pair.Password)
   142  		if err != nil {
   143  			general.Warningf("fail to decode password, err: %v", err)
   144  			continue
   145  		}
   146  		newAuthPairs[pair.Username] = string(p)
   147  	}
   148  	b.mutex.Lock()
   149  	b.authPairs = newAuthPairs
   150  	b.mutex.Unlock()
   151  }