github.com/argoproj/argo-cd/v3@v3.2.1/util/localconfig/localconfig.go (about)

     1  package localconfig
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"strings"
     9  
    10  	"github.com/golang-jwt/jwt/v5"
    11  
    12  	"github.com/argoproj/argo-cd/v3/util/config"
    13  )
    14  
    15  // LocalConfig is a local Argo CD config file
    16  type LocalConfig struct {
    17  	CurrentContext string       `json:"current-context"`
    18  	Contexts       []ContextRef `json:"contexts"`
    19  	Servers        []Server     `json:"servers"`
    20  	Users          []User       `json:"users"`
    21  	PromptsEnabled bool         `json:"prompts-enabled"`
    22  }
    23  
    24  // ContextRef is a reference to a Server and User for an API client
    25  type ContextRef struct {
    26  	Name   string `json:"name"`
    27  	Server string `json:"server"`
    28  	User   string `json:"user"`
    29  }
    30  
    31  // Context is the resolved Server and User objects resolved
    32  type Context struct {
    33  	Name   string
    34  	Server Server
    35  	User   User
    36  }
    37  
    38  // Server contains Argo CD server information
    39  type Server struct {
    40  	// Server is the Argo CD server address
    41  	Server string `json:"server"`
    42  	// Insecure indicates to connect to the server over TLS insecurely
    43  	Insecure bool `json:"insecure,omitempty"`
    44  	// GRPCWeb indicates to connect to the server using gRPC Web protocol
    45  	GRPCWeb bool `json:"grpc-web,omitempty"`
    46  	// GRPCWebRootPath indicates to connect to the server using gRPC Web protocol with this root path
    47  	GRPCWebRootPath string `json:"grpc-web-root-path"`
    48  	// CACertificateAuthorityData is the base64 string of a PEM encoded certificate
    49  	// TODO: not yet implemented
    50  	CACertificateAuthorityData string `json:"certificate-authority-data,omitempty"`
    51  	// ClientCertificateData is the base64 string of a PEM encoded certificate used to authenticate the client
    52  	ClientCertificateData string `json:"client-certificate-data,omitempty"`
    53  	// ClientCertificateKeyData is the base64 string of a PEM encoded private key of the client certificate
    54  	ClientCertificateKeyData string `json:"client-certificate-key-data,omitempty"`
    55  	// PlainText indicates to connect with TLS disabled
    56  	PlainText bool `json:"plain-text,omitempty"`
    57  	// Core indicates to talk to Kubernetes API without using Argo CD API server
    58  	Core bool `json:"core,omitempty"`
    59  }
    60  
    61  // User contains user authentication information
    62  type User struct {
    63  	Name         string `json:"name"`
    64  	AuthToken    string `json:"auth-token,omitempty"`
    65  	RefreshToken string `json:"refresh-token,omitempty"`
    66  }
    67  
    68  // Claims returns the standard claims from the JWT claims
    69  func (u *User) Claims() (*jwt.RegisteredClaims, error) {
    70  	parser := jwt.NewParser(jwt.WithoutClaimsValidation())
    71  	claims := jwt.RegisteredClaims{}
    72  	_, _, err := parser.ParseUnverified(u.AuthToken, &claims)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	return &claims, nil
    77  }
    78  
    79  // ReadLocalConfig loads up the local configuration file. Returns nil if config does not exist
    80  func ReadLocalConfig(path string) (*LocalConfig, error) {
    81  	var err error
    82  	var localconfig LocalConfig
    83  
    84  	// check file permission only when argocd config exists
    85  	if fi, err := os.Stat(path); err == nil {
    86  		err = getFilePermission(fi)
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  	}
    91  
    92  	err = config.UnmarshalLocalFile(path, &localconfig)
    93  	if os.IsNotExist(err) {
    94  		return nil, nil
    95  	}
    96  	err = ValidateLocalConfig(localconfig)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	return &localconfig, nil
   101  }
   102  
   103  func ValidateLocalConfig(config LocalConfig) error {
   104  	if config.CurrentContext == "" {
   105  		return nil
   106  	}
   107  	if _, err := config.ResolveContext(config.CurrentContext); err != nil {
   108  		return fmt.Errorf("local config invalid: %w", err)
   109  	}
   110  	return nil
   111  }
   112  
   113  // WriteLocalConfig writes a new local configuration file.
   114  func WriteLocalConfig(localconfig LocalConfig, configPath string) error {
   115  	err := os.MkdirAll(path.Dir(configPath), os.ModePerm)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	return config.MarshalLocalYAMLFile(configPath, localconfig)
   120  }
   121  
   122  func DeleteLocalConfig(configPath string) error {
   123  	_, err := os.Stat(configPath)
   124  	if os.IsNotExist(err) {
   125  		return err
   126  	}
   127  	return os.Remove(configPath)
   128  }
   129  
   130  // ResolveContext resolves the specified context. If unspecified, resolves the current context
   131  func (l *LocalConfig) ResolveContext(name string) (*Context, error) {
   132  	if name == "" {
   133  		if l.CurrentContext == "" {
   134  			return nil, errors.New("local config: current-context unset")
   135  		}
   136  		name = l.CurrentContext
   137  	}
   138  	for _, ctx := range l.Contexts {
   139  		if ctx.Name != name {
   140  			continue
   141  		}
   142  		server, err := l.GetServer(ctx.Server)
   143  		if err != nil {
   144  			return nil, err
   145  		}
   146  		user, err := l.GetUser(ctx.User)
   147  		if err != nil {
   148  			return nil, err
   149  		}
   150  		return &Context{
   151  			Name:   ctx.Name,
   152  			Server: *server,
   153  			User:   *user,
   154  		}, nil
   155  	}
   156  	return nil, fmt.Errorf("Context '%s' undefined", name)
   157  }
   158  
   159  func (l *LocalConfig) GetServer(name string) (*Server, error) {
   160  	for _, s := range l.Servers {
   161  		if s.Server == name {
   162  			return &s, nil
   163  		}
   164  	}
   165  	return nil, fmt.Errorf("Server '%s' undefined", name)
   166  }
   167  
   168  func (l *LocalConfig) UpsertServer(server Server) {
   169  	for i, s := range l.Servers {
   170  		if s.Server == server.Server {
   171  			l.Servers[i] = server
   172  			return
   173  		}
   174  	}
   175  	l.Servers = append(l.Servers, server)
   176  }
   177  
   178  // Returns true if server was removed successfully
   179  func (l *LocalConfig) RemoveServer(serverName string) bool {
   180  	for i, s := range l.Servers {
   181  		if s.Server == serverName {
   182  			l.Servers = append(l.Servers[:i], l.Servers[i+1:]...)
   183  			return true
   184  		}
   185  	}
   186  	return false
   187  }
   188  
   189  func (l *LocalConfig) GetUser(name string) (*User, error) {
   190  	for _, u := range l.Users {
   191  		if u.Name == name {
   192  			return &u, nil
   193  		}
   194  	}
   195  	return nil, fmt.Errorf("User '%s' undefined", name)
   196  }
   197  
   198  func (l *LocalConfig) UpsertUser(user User) {
   199  	for i, u := range l.Users {
   200  		if u.Name == user.Name {
   201  			l.Users[i] = user
   202  			return
   203  		}
   204  	}
   205  	l.Users = append(l.Users, user)
   206  }
   207  
   208  // Returns true if user was removed successfully
   209  func (l *LocalConfig) RemoveUser(serverName string) bool {
   210  	for i, u := range l.Users {
   211  		if u.Name == serverName {
   212  			l.Users = append(l.Users[:i], l.Users[i+1:]...)
   213  			return true
   214  		}
   215  	}
   216  	return false
   217  }
   218  
   219  // Returns true if user was removed successfully
   220  func (l *LocalConfig) RemoveToken(serverName string) bool {
   221  	for i, u := range l.Users {
   222  		if u.Name == serverName {
   223  			l.Users[i].RefreshToken = ""
   224  			l.Users[i].AuthToken = ""
   225  			return true
   226  		}
   227  	}
   228  	return false
   229  }
   230  
   231  func (l *LocalConfig) UpsertContext(context ContextRef) {
   232  	for i, c := range l.Contexts {
   233  		if c.Name == context.Name {
   234  			l.Contexts[i] = context
   235  			return
   236  		}
   237  	}
   238  	l.Contexts = append(l.Contexts, context)
   239  }
   240  
   241  // Returns true if context was removed successfully
   242  func (l *LocalConfig) RemoveContext(serverName string) (string, bool) {
   243  	for i, c := range l.Contexts {
   244  		if c.Name == serverName {
   245  			l.Contexts = append(l.Contexts[:i], l.Contexts[i+1:]...)
   246  			return c.Server, true
   247  		}
   248  	}
   249  	return "", false
   250  }
   251  
   252  func (l *LocalConfig) IsEmpty() bool {
   253  	return len(l.Servers) == 0
   254  }
   255  
   256  // DefaultConfigDir returns the local configuration path for settings such as cached authentication tokens.
   257  func DefaultConfigDir() (string, error) {
   258  	// Manually defined config directory
   259  	configDir := os.Getenv("ARGOCD_CONFIG_DIR")
   260  	if configDir != "" {
   261  		return configDir, nil
   262  	}
   263  
   264  	homeDir, err := getHomeDir()
   265  	if err != nil {
   266  		return "", err
   267  	}
   268  
   269  	// Legacy config directory
   270  	// Use it if it already exists
   271  	legacyConfigDir := path.Join(homeDir, ".argocd")
   272  
   273  	if _, err := os.Stat(legacyConfigDir); err == nil {
   274  		return legacyConfigDir, nil
   275  	}
   276  
   277  	// Manually configured XDG config home
   278  	if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
   279  		return path.Join(xdgConfigHome, "argocd"), nil
   280  	}
   281  
   282  	// XDG config home fallback
   283  	return path.Join(homeDir, ".config", "argocd"), nil
   284  }
   285  
   286  func getHomeDir() (string, error) {
   287  	homeDir, err := os.UserHomeDir()
   288  	if err != nil {
   289  		return "", err
   290  	}
   291  
   292  	return homeDir, nil
   293  }
   294  
   295  // DefaultLocalConfigPath returns the local configuration path for settings such as cached authentication tokens.
   296  func DefaultLocalConfigPath() (string, error) {
   297  	dir, err := DefaultConfigDir()
   298  	if err != nil {
   299  		return "", err
   300  	}
   301  	return path.Join(dir, "config"), nil
   302  }
   303  
   304  // Get username from subject in a claim
   305  func GetUsername(subject string) string {
   306  	parts := strings.Split(subject, ":")
   307  	if len(parts) > 0 {
   308  		return parts[0]
   309  	}
   310  	return subject
   311  }
   312  
   313  func GetPromptsEnabled(useCLIOpts bool) bool {
   314  	if useCLIOpts {
   315  		forcePromptsEnabled := config.GetFlag("prompts-enabled", "")
   316  
   317  		if forcePromptsEnabled != "" {
   318  			return forcePromptsEnabled == "true"
   319  		}
   320  	}
   321  
   322  	defaultLocalConfigPath, err := DefaultLocalConfigPath()
   323  	if err != nil {
   324  		return false
   325  	}
   326  
   327  	localConfigPath := config.GetFlag("config", defaultLocalConfigPath)
   328  
   329  	localConfig, err := ReadLocalConfig(localConfigPath)
   330  	if localConfig == nil || err != nil {
   331  		return false
   332  	}
   333  
   334  	return localConfig.PromptsEnabled
   335  }