github.com/versent/saml2aws@v2.17.0+incompatible/pkg/cfg/cfg.go (about)

     1  package cfg
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  
     7  	"github.com/mitchellh/go-homedir"
     8  	"github.com/pkg/errors"
     9  	ini "gopkg.in/ini.v1"
    10  )
    11  
    12  // ErrIdpAccountNotFound returned if the idp account is not found in the configuration file
    13  var ErrIdpAccountNotFound = errors.New("IDP account not found, run configure to set it up")
    14  
    15  const (
    16  	// DefaultConfigPath the default saml2aws configuration path
    17  	DefaultConfigPath = "~/.saml2aws"
    18  
    19  	// DefaultAmazonWebservicesURN URN used when authenticating to aws using SAML
    20  	// NOTE: This only needs to be changed to log into GovCloud
    21  	DefaultAmazonWebservicesURN = "urn:amazon:webservices"
    22  
    23  	// DefaultSessionDuration this is the default session duration which can be overridden in the AWS console
    24  	// see https://aws.amazon.com/blogs/security/enable-federated-api-access-to-your-aws-resources-for-up-to-12-hours-using-iam-roles/
    25  	DefaultSessionDuration = 3600
    26  
    27  	// DefaultProfile this is the default profile name used to save the credentials in the aws cli
    28  	DefaultProfile = "saml"
    29  )
    30  
    31  // IDPAccount saml IDP account
    32  type IDPAccount struct {
    33  	AppID                string `ini:"app_id"` // used by OneLogin and AzureAD
    34  	URL                  string `ini:"url"`
    35  	Username             string `ini:"username"`
    36  	Provider             string `ini:"provider"`
    37  	MFA                  string `ini:"mfa"`
    38  	SkipVerify           bool   `ini:"skip_verify"`
    39  	Timeout              int    `ini:"timeout"`
    40  	AmazonWebservicesURN string `ini:"aws_urn"`
    41  	SessionDuration      int    `ini:"aws_session_duration"`
    42  	Profile              string `ini:"aws_profile"`
    43  	ResourceID           string `ini:"resource_id"` // used by F5APM
    44  	Subdomain            string `ini:"subdomain"`   // used by OneLogin
    45  	RoleARN              string `ini:"role_arn"`
    46  }
    47  
    48  func (ia IDPAccount) String() string {
    49  	var appID string
    50  	var policyID string
    51  	switch ia.Provider {
    52  	case "OneLogin":
    53  		appID = fmt.Sprintf(`
    54    AppID: %s
    55    Subdomain: %s`, ia.AppID, ia.Subdomain)
    56  	case "F5APM":
    57  		policyID = fmt.Sprintf("\n  ResourceID: %s", ia.ResourceID)
    58  	case "AzureAD":
    59  		appID = fmt.Sprintf(`
    60    AppID: %s`, ia.AppID)
    61  	}
    62  
    63  	return fmt.Sprintf(`account {%s%s
    64    URL: %s
    65    Username: %s
    66    Provider: %s
    67    MFA: %s
    68    SkipVerify: %v
    69    AmazonWebservicesURN: %s
    70    SessionDuration: %d
    71    Profile: %s
    72    RoleARN: %s
    73  }`, appID, policyID, ia.URL, ia.Username, ia.Provider, ia.MFA, ia.SkipVerify, ia.AmazonWebservicesURN, ia.SessionDuration, ia.Profile, ia.RoleARN)
    74  }
    75  
    76  // Validate validate the required / expected fields are set
    77  func (ia *IDPAccount) Validate() error {
    78  	switch ia.Provider {
    79  	case "OneLogin":
    80  		if ia.AppID == "" {
    81  			return errors.New("app ID empty in idp account")
    82  		}
    83  		if ia.Subdomain == "" {
    84  			return errors.New("subdomain empty in idp account")
    85  		}
    86  	case "F5APM":
    87  		if ia.ResourceID == "" {
    88  			return errors.New("Resource ID empty in idp account")
    89  		}
    90  	case "AzureAD":
    91  		if ia.AppID == "" {
    92  			return errors.New("app ID empty in idp account")
    93  		}
    94  	}
    95  
    96  	if ia.URL == "" {
    97  		return errors.New("URL empty in idp account")
    98  	}
    99  
   100  	_, err := url.Parse(ia.URL)
   101  	if err != nil {
   102  		return errors.New("URL parse failed")
   103  	}
   104  
   105  	if ia.Provider == "" {
   106  		return errors.New("Provider empty in idp account")
   107  	}
   108  
   109  	if ia.MFA == "" {
   110  		return errors.New("MFA empty in idp account")
   111  	}
   112  
   113  	if ia.Profile == "" {
   114  		return errors.New("Profile empty in idp account")
   115  	}
   116  
   117  	return nil
   118  }
   119  
   120  // NewIDPAccount Create an idp account and fill in any default fields with sane values
   121  func NewIDPAccount() *IDPAccount {
   122  	return &IDPAccount{
   123  		AmazonWebservicesURN: DefaultAmazonWebservicesURN,
   124  		SessionDuration:      DefaultSessionDuration,
   125  		Profile:              DefaultProfile,
   126  	}
   127  }
   128  
   129  // ConfigManager manage the various IDP account settings
   130  type ConfigManager struct {
   131  	configPath string
   132  }
   133  
   134  // NewConfigManager build a new config manager and optionally override the config path
   135  func NewConfigManager(configFile string) (*ConfigManager, error) {
   136  
   137  	if configFile == "" {
   138  		configFile = DefaultConfigPath
   139  	}
   140  
   141  	configPath, err := homedir.Expand(configFile)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	return &ConfigManager{configPath}, nil
   147  }
   148  
   149  // SaveIDPAccount save idp account
   150  func (cm *ConfigManager) SaveIDPAccount(idpAccountName string, account *IDPAccount) error {
   151  
   152  	if err := account.Validate(); err != nil {
   153  		return errors.Wrap(err, "Account validation failed")
   154  	}
   155  
   156  	cfg, err := ini.LoadSources(ini.LoadOptions{Loose: true}, cm.configPath)
   157  	if err != nil {
   158  		return errors.Wrap(err, "Unable to load configuration file")
   159  	}
   160  
   161  	newSec, err := cfg.NewSection(idpAccountName)
   162  	if err != nil {
   163  		return errors.Wrap(err, "Unable to build a new section in configuration file")
   164  	}
   165  
   166  	err = newSec.ReflectFrom(account)
   167  	if err != nil {
   168  		return errors.Wrap(err, "Unable to save account to configuration file")
   169  	}
   170  
   171  	err = cfg.SaveTo(cm.configPath)
   172  	if err != nil {
   173  		return errors.Wrap(err, "Failed to save configuration file")
   174  	}
   175  	return nil
   176  }
   177  
   178  // LoadIDPAccount load the idp account and default to an empty one if it doesn't exist
   179  func (cm *ConfigManager) LoadIDPAccount(idpAccountName string) (*IDPAccount, error) {
   180  
   181  	cfg, err := ini.LoadSources(ini.LoadOptions{Loose: true}, cm.configPath)
   182  	if err != nil {
   183  		return nil, errors.Wrap(err, "Unable to load configuration file")
   184  	}
   185  
   186  	// attempt to map a specific idp account by name
   187  	// this will return an empty account if one is not found by the given name
   188  	account, err := readAccount(idpAccountName, cfg)
   189  	if err != nil {
   190  		return nil, errors.Wrap(err, "Unable to read idp account")
   191  	}
   192  
   193  	return account, nil
   194  }
   195  
   196  func readAccount(idpAccountName string, cfg *ini.File) (*IDPAccount, error) {
   197  
   198  	account := NewIDPAccount()
   199  
   200  	sec := cfg.Section(idpAccountName)
   201  
   202  	err := sec.MapTo(account)
   203  	if err != nil {
   204  		return nil, errors.Wrap(err, "Unable to map account")
   205  	}
   206  
   207  	return account, nil
   208  }