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 }