github.com/verrazzano/verrazzano@v1.7.0/authproxy/src/config/config.go (about)

     1  // Copyright (c) 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package config
     5  
     6  import (
     7  	"os"
     8  	"sync"
     9  	"sync/atomic"
    10  	"time"
    11  
    12  	"go.uber.org/zap"
    13  )
    14  
    15  // these can be changed for unit testing
    16  var (
    17  	serviceURLFilename  = "/etc/config/oidcServiceURL"
    18  	externalURLFilename = "/etc/config/oidcExternalURL"
    19  	clientIDFilename    = "/etc/config/oidcClientID"
    20  
    21  	watchInterval = time.Minute
    22  	keepWatching  atomic.Bool
    23  )
    24  
    25  var (
    26  	serviceURL  string
    27  	externalURL string
    28  	clientID    string
    29  
    30  	serviceURLFileModTime  time.Time
    31  	externalURLFileModTime time.Time
    32  	clientIDFileModTime    time.Time
    33  
    34  	mutex sync.RWMutex
    35  )
    36  
    37  // GetServiceURL returns the in-cluster service URL of the OIDC provider
    38  func GetServiceURL() string {
    39  	mutex.RLock()
    40  	defer mutex.RUnlock()
    41  	return serviceURL
    42  }
    43  
    44  // GetExternalURL returns the external URL of the OIDC provider
    45  func GetExternalURL() string {
    46  	mutex.RLock()
    47  	defer mutex.RUnlock()
    48  	return externalURL
    49  }
    50  
    51  // GetClientID returns the client ID
    52  func GetClientID() string {
    53  	mutex.RLock()
    54  	defer mutex.RUnlock()
    55  	return clientID
    56  }
    57  
    58  // loadServiceURL loads the in-cluster service URL from a file and stores the file modification time
    59  func loadServiceURL() error {
    60  	mutex.Lock()
    61  	defer mutex.Unlock()
    62  
    63  	value, modTime, err := loadConfigValue(serviceURLFilename)
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	serviceURL = value
    69  	serviceURLFileModTime = *modTime
    70  	return nil
    71  }
    72  
    73  // loadExternalURL loads the external URL from a file and stores the file modification time
    74  func loadExternalURL() error {
    75  	mutex.Lock()
    76  	defer mutex.Unlock()
    77  
    78  	value, modTime, err := loadConfigValue(externalURLFilename)
    79  	if err != nil {
    80  		return err
    81  	}
    82  
    83  	externalURL = value
    84  	externalURLFileModTime = *modTime
    85  	return nil
    86  }
    87  
    88  // loadClientID loads the client ID from a file and stores the file modification time
    89  func loadClientID() error {
    90  	mutex.Lock()
    91  	defer mutex.Unlock()
    92  
    93  	value, modTime, err := loadConfigValue(clientIDFilename)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	clientID = value
    99  	clientIDFileModTime = *modTime
   100  	return nil
   101  }
   102  
   103  // loadConfigValue loads a configuration value from a file and stores the file modification time
   104  func loadConfigValue(filename string) (string, *time.Time, error) {
   105  	bytes, err := os.ReadFile(filename)
   106  	if err != nil {
   107  		return "", nil, err
   108  	}
   109  
   110  	fileInfo, err := os.Stat(filename)
   111  	if err != nil {
   112  		return "", nil, err
   113  	}
   114  
   115  	modTime := fileInfo.ModTime()
   116  	return string(bytes), &modTime, nil
   117  }
   118  
   119  // InitConfiguration loads the configuration from files and starts a goroutine to watch for configuration changes and reloads
   120  // config values when changes are detected
   121  func InitConfiguration(log *zap.SugaredLogger) error {
   122  	if envVal := os.Getenv("OVERRIDE_SVC_URL"); envVal != "" {
   123  		serviceURL = envVal
   124  	}
   125  	if envVal := os.Getenv("OVERRIDE_EXTERNAL_URL"); envVal != "" {
   126  		externalURL = envVal
   127  	}
   128  	if externalURL != "" && serviceURL != "" {
   129  		return nil
   130  	}
   131  	if err := loadServiceURL(); err != nil {
   132  		log.Errorf("Failed to load Service URL: %v", err)
   133  		return err
   134  	}
   135  	if err := loadExternalURL(); err != nil {
   136  		log.Errorf("Failed to load External URL: %v", err)
   137  		return err
   138  	}
   139  	if err := loadClientID(); err != nil {
   140  		log.Errorf("Failed to load Client ID: %v", err)
   141  		return err
   142  	}
   143  
   144  	keepWatching.Store(true)
   145  	go watchConfigForChanges(log)
   146  	return nil
   147  }
   148  
   149  // watchConfigForChanges watches the configuration files for changes and reloads as necessary. This function generally
   150  // runs forever but the keepWatching atomic bool can be set to false in unit tests to stop the loop.
   151  func watchConfigForChanges(log *zap.SugaredLogger) {
   152  	for keepWatching.Load() {
   153  		if err := reloadConfigWhenChanged(log); err != nil {
   154  			log.Warnf("Error reloading configuration: %v", err)
   155  		}
   156  		time.Sleep(watchInterval)
   157  	}
   158  }
   159  
   160  // reloadConfigWhenChanged compares the config file modification times and reloads config values
   161  func reloadConfigWhenChanged(log *zap.SugaredLogger) error {
   162  	fileInfo, err := os.Stat(serviceURLFilename)
   163  	if err != nil {
   164  		return err
   165  	}
   166  	if fileInfo.ModTime().After(serviceURLFileModTime) {
   167  		// file has changed
   168  		log.Debugf("Detected change in file %s, reloading contents", serviceURLFilename)
   169  		if err := loadServiceURL(); err != nil {
   170  			return err
   171  		}
   172  	}
   173  
   174  	fileInfo, err = os.Stat(externalURLFilename)
   175  	if err != nil {
   176  		return err
   177  	}
   178  	if fileInfo.ModTime().After(externalURLFileModTime) {
   179  		// file has changed
   180  		log.Debugf("Detected change in file %s, reloading contents", externalURLFilename)
   181  		if err := loadExternalURL(); err != nil {
   182  			return err
   183  		}
   184  	}
   185  
   186  	fileInfo, err = os.Stat(clientIDFilename)
   187  	if err != nil {
   188  		return err
   189  	}
   190  	if fileInfo.ModTime().After(clientIDFileModTime) {
   191  		// file has changed
   192  		log.Debugf("Detected change in file %s, reloading contents", clientIDFilename)
   193  		if err := loadClientID(); err != nil {
   194  			return err
   195  		}
   196  	}
   197  
   198  	return nil
   199  }