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 }