pkg/cfg/cfg.go (164 lines of code) (raw):
package cfg
import (
"fmt"
"net/url"
"github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
ini "gopkg.in/ini.v1"
)
// ErrIdpAccountNotFound returned if the idp account is not found in the configuration file
var ErrIdpAccountNotFound = errors.New("IDP account not found, run configure to set it up")
const (
// DefaultConfigPath the default saml2alibabacloud configuration path
DefaultConfigPath = "~/.saml2alibabacloud"
// DefaultAlibabaCloudURN URN used when authenticating to AlibabaCloud using SAML
// NOTE: Currently, this does not need to be changed
DefaultAlibabaCloudURN = "urn:alibaba:cloudcomputing"
// DefaultSessionDuration this is the default session duration which can be overridden in the AlibabaCloud console
// see https://www.alibabacloud.com/help/doc-detail/166256.htm
DefaultSessionDuration = 3600
// DefaultProfile this is the default profile name used to save the credentials in the `aliyun` cli
// see https://www.alibabacloud.com/help/doc-detail/121259.htm
DefaultProfile = "saml"
)
// IDPAccount saml IDP account
type IDPAccount struct {
AppID string `ini:"app_id"` // used by OneLogin and AzureAD
URL string `ini:"url"`
Username string `ini:"username"`
Provider string `ini:"provider"`
MFA string `ini:"mfa"`
SkipVerify bool `ini:"skip_verify"`
Timeout int `ini:"timeout"`
AlibabaCloudURN string `ini:"alibabacloud_urn"`
SessionDuration int `ini:"alibabacloud_session_duration"`
Profile string `ini:"alibabacloud_profile"`
ResourceID string `ini:"resource_id"` // used by F5APM
Subdomain string `ini:"subdomain"` // used by OneLogin
RoleARN string `ini:"role_arn"`
Region string `ini:"region"`
HTTPAttemptsCount string `ini:"http_attempts_count"`
HTTPRetryDelay string `ini:"http_retry_delay"`
BrowserType string `ini:"browser_type,omitempty"` // used by 'Browser' Provider
BrowserExecutablePath string `ini:"browser_executable_path,omitempty"` // used by 'Browser' Provider
BrowserAutoFill bool `ini:"browser_autofill,omitempty"` // used by 'Browser' Provider
DownloadBrowser bool `ini:"download_browser_driver"` // used by browser
BrowserDriverDir string `ini:"browser_driver_dir,omitempty"` // used by browser; hide from user if not set
Headless bool `ini:"headless"` // used by browser
Prompter string `ini:"prompter"`
}
func (ia IDPAccount) String() string {
var appID string
var policyID string
switch ia.Provider {
case "OneLogin":
appID = fmt.Sprintf(`
AppID: %s
Subdomain: %s`, ia.AppID, ia.Subdomain)
case "F5APM":
policyID = fmt.Sprintf("\n ResourceID: %s", ia.ResourceID)
case "AzureAD":
appID = fmt.Sprintf(`
AppID: %s`, ia.AppID)
}
return fmt.Sprintf(`account {%s%s
URL: %s
Username: %s
Provider: %s
MFA: %s
SkipVerify: %v
SessionDuration: %d
Profile: %s
RoleARN: %s
Region: %s
}`, appID, policyID, ia.URL, ia.Username, ia.Provider, ia.MFA, ia.SkipVerify, ia.SessionDuration, ia.Profile, ia.RoleARN, ia.Region)
}
// Validate validate the required / expected fields are set
func (ia *IDPAccount) Validate() error {
switch ia.Provider {
case "OneLogin":
if ia.AppID == "" {
return errors.New("app ID empty in idp account")
}
if ia.Subdomain == "" {
return errors.New("subdomain empty in idp account")
}
case "F5APM":
if ia.ResourceID == "" {
return errors.New("Resource ID empty in idp account")
}
case "AzureAD":
if ia.AppID == "" {
return errors.New("app ID empty in idp account")
}
}
if ia.URL == "" {
return errors.New("URL empty in idp account")
}
_, err := url.Parse(ia.URL)
if err != nil {
return errors.New("URL parse failed")
}
if ia.Provider == "" {
return errors.New("Provider empty in idp account")
}
if ia.MFA == "" {
return errors.New("MFA empty in idp account")
}
if ia.Profile == "" {
return errors.New("Profile empty in idp account")
}
return nil
}
// NewIDPAccount Create an idp account and fill in any default fields with sane values
func NewIDPAccount() *IDPAccount {
return &IDPAccount{
AlibabaCloudURN: DefaultAlibabaCloudURN,
SessionDuration: DefaultSessionDuration,
Profile: DefaultProfile,
}
}
// ConfigManager manage the various IDP account settings
type ConfigManager struct {
configPath string
}
// NewConfigManager build a new config manager and optionally override the config path
func NewConfigManager(configFile string) (*ConfigManager, error) {
if configFile == "" {
configFile = DefaultConfigPath
}
configPath, err := homedir.Expand(configFile)
if err != nil {
return nil, err
}
return &ConfigManager{configPath}, nil
}
// SaveIDPAccount save idp account
func (cm *ConfigManager) SaveIDPAccount(idpAccountName string, account *IDPAccount) error {
if err := account.Validate(); err != nil {
return errors.Wrap(err, "Account validation failed")
}
cfg, err := ini.LoadSources(ini.LoadOptions{Loose: true}, cm.configPath)
if err != nil {
return errors.Wrap(err, "Unable to load configuration file")
}
newSec, err := cfg.NewSection(idpAccountName)
if err != nil {
return errors.Wrap(err, "Unable to build a new section in configuration file")
}
err = newSec.ReflectFrom(account)
if err != nil {
return errors.Wrap(err, "Unable to save account to configuration file")
}
err = cfg.SaveTo(cm.configPath)
if err != nil {
return errors.Wrap(err, "Failed to save configuration file")
}
return nil
}
// LoadIDPAccount load the idp account and default to an empty one if it doesn't exist
func (cm *ConfigManager) LoadIDPAccount(idpAccountName string) (*IDPAccount, error) {
cfg, err := ini.LoadSources(ini.LoadOptions{Loose: true}, cm.configPath)
if err != nil {
return nil, errors.Wrap(err, "Unable to load configuration file")
}
// attempt to map a specific idp account by name
// this will return an empty account if one is not found by the given name
account, err := readAccount(idpAccountName, cfg)
if err != nil {
return nil, errors.Wrap(err, "Unable to read idp account")
}
return account, nil
}
func readAccount(idpAccountName string, cfg *ini.File) (*IDPAccount, error) {
account := NewIDPAccount()
sec := cfg.Section(idpAccountName)
err := sec.MapTo(account)
if err != nil {
return nil, errors.Wrap(err, "Unable to map account")
}
return account, nil
}