diff --git a/README.md b/README.md index a6b385a..6522141 100644 --- a/README.md +++ b/README.md @@ -453,7 +453,8 @@ These settings are optional unless marked otherwise: | Name | Description | Command line flag | ENV var and .env file value | |-----|-----|-----|-----| | Key ID (kid) (**required**) | The ID of the key stored in the service app | `--key-id [value]` | `OKTA_AWSCLI_KEY_ID` | -| Private Key (**required**) | PEM (pkcs#1 or pkcs#9) private key whose public key is stored on the service app | `--private-key [value]` | `OKTA_AWSCLI_PRIVATE_KEY` | +| Private Key (**required** in lieu of private key file) | PEM (pkcs#1 or pkcs#9) private key whose public key is stored on the service app | `--private-key [value]` | `OKTA_AWSCLI_PRIVATE_KEY` | +| Private Key File (**required** in lieu of private key) | File holding PEM (pkcs#1 or pkcs#9) private key whose public key is stored on the service app | `--private-key-file [value]` | `OKTA_AWSCLI_PRIVATE_KEY_FILE` | | Authorization Server ID | The ID of the Okta authorization server, set ID for a custom authorization server, will use default otherwise. Default `default` | `--authz-id [value]` | `OKTA_AWSCLI_AUTHZ_ID` | | Custom scope name | The custom scope established in the custom authorization server. Default `okta-m2m-access` | `--custom-scope [value]` | `OKTA_AWSCLI_CUSTOM_SCOPE` | diff --git a/cmd/root/m2m/m2m.go b/cmd/root/m2m/m2m.go index eeab36a..6a7df7f 100644 --- a/cmd/root/m2m/m2m.go +++ b/cmd/root/m2m/m2m.go @@ -37,9 +37,16 @@ var ( Name: config.PrivateKeyFlag, Short: "k", Value: "", - Usage: "Private Key", + Usage: "Private Key (string value)", EnvVar: config.PrivateKeyEnvVar, }, + { + Name: config.PrivateKeyFileFlag, + Short: "b", + Value: "", + Usage: "Private Key File", + EnvVar: config.PrivateKeyFileEnvVar, + }, { Name: config.CustomScopeFlag, Short: "m", @@ -55,7 +62,7 @@ var ( EnvVar: config.AuthzIDEnvVar, }, } - requiredFlags = []string{"org-domain", "oidc-client-id", "aws-iam-role", "key-id", "private-key"} + requiredFlags = []interface{}{"org-domain", "oidc-client-id", "aws-iam-role", "key-id", []string{"private-key", "private-key-file"}} ) // NewM2MCommand Sets up the m2m cobra sub command diff --git a/cmd/root/web/web.go b/cmd/root/web/web.go index 24fbf01..7df3b96 100644 --- a/cmd/root/web/web.go +++ b/cmd/root/web/web.go @@ -69,7 +69,7 @@ var ( EnvVar: config.AllProfilesEnvVar, }, } - requiredFlags = []string{"org-domain", "oidc-client-id"} + requiredFlags = []interface{}{"org-domain", "oidc-client-id"} ) // NewWebCommand Sets up the web cobra sub command diff --git a/internal/config/config.go b/internal/config/config.go index 12ac0da..1de0a66 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -82,6 +82,8 @@ const ( OrgDomainFlag = "org-domain" // PrivateKeyFlag cli flag const PrivateKeyFlag = "private-key" + // PrivateKeyFileFlag cli flag const + PrivateKeyFileFlag = "private-key-file" // KeyIDFlag cli flag const KeyIDFlag = "key-id" // ProfileFlag cli flag const @@ -145,6 +147,8 @@ const ( OpenBrowserCommandEnvVar = "OKTA_AWSCLI_OPEN_BROWSER_COMMAND" // PrivateKeyEnvVar env var const PrivateKeyEnvVar = "OKTA_AWSCLI_PRIVATE_KEY" + // PrivateKeyFileEnvVar env var const + PrivateKeyFileEnvVar = "OKTA_AWSCLI_PRIVATE_KEY_FILE" // KeyIDEnvVar env var const KeyIDEnvVar = "OKTA_AWSCLI_KEY_ID" // ProfileEnvVar env var const @@ -207,6 +211,7 @@ type Config struct { openBrowserCommand string orgDomain string privateKey string + privateKeyFile string profile string qrCode bool writeAWSCredentials bool @@ -236,6 +241,7 @@ type Attributes struct { OpenBrowserCommand string OrgDomain string PrivateKey string + PrivateKeyFile string Profile string QRCode bool WriteAWSCredentials bool @@ -274,6 +280,7 @@ func NewConfig(attrs *Attributes) (*Config, error) { openBrowser: attrs.OpenBrowser, openBrowserCommand: attrs.OpenBrowserCommand, privateKey: attrs.PrivateKey, + privateKeyFile: attrs.PrivateKeyFile, keyID: attrs.KeyID, profile: attrs.Profile, qrCode: attrs.QRCode, @@ -325,6 +332,7 @@ func readConfig() (Attributes, error) { OpenBrowserCommand: viper.GetString(OpenBrowserCommandFlag), OrgDomain: viper.GetString(OrgDomainFlag), PrivateKey: viper.GetString(PrivateKeyFlag), + PrivateKeyFile: viper.GetString(PrivateKeyFileFlag), KeyID: viper.GetString(KeyIDFlag), Profile: viper.GetString(ProfileFlag), QRCode: viper.GetBool(QRCodeFlag), @@ -376,6 +384,9 @@ func readConfig() (Attributes, error) { if attrs.PrivateKey == "" { attrs.PrivateKey = viper.GetString(downCase(PrivateKeyEnvVar)) } + if attrs.PrivateKeyFile == "" { + attrs.PrivateKeyFile = viper.GetString(downCase(PrivateKeyFileEnvVar)) + } if attrs.KeyID == "" { attrs.KeyID = viper.GetString(downCase(KeyIDEnvVar)) } @@ -724,6 +735,17 @@ func (c *Config) SetPrivateKey(privateKey string) error { return nil } +// PrivateKeyFile -- +func (c *Config) PrivateKeyFile() string { + return c.privateKeyFile +} + +// SetPrivateKeyFile -- +func (c *Config) SetPrivateKeyFile(privateKeyFile string) error { + c.privateKeyFile = privateKeyFile + return nil +} + // KeyID -- func (c *Config) KeyID() string { return c.keyID diff --git a/internal/flag/flag.go b/internal/flag/flag.go index 183278d..1317f8f 100644 --- a/internal/flag/flag.go +++ b/internal/flag/flag.go @@ -110,12 +110,28 @@ func MakeFlagBindings(cmd *cobra.Command, flags []Flag, persistent bool) { } // CheckRequiredFlags Checks if flags in the list are all set in Viper -func CheckRequiredFlags(flags []string) error { +func CheckRequiredFlags(flags []interface{}) error { unsetFlags := []string{} for _, f := range flags { - altName := altFlagName(f) - if !viper.GetViper().IsSet(f) && !viper.GetViper().IsSet(altName) { - unsetFlags = append(unsetFlags, fmt.Sprintf(" --%s", f)) + if arr, ok := f.([]string); ok { + found := false + for _, flag := range arr { + altName := altFlagName(flag) + if viper.GetViper().IsSet(flag) || viper.GetViper().IsSet(altName) { + found = true + } + } + + if !found { + unsetFlags = append(unsetFlags, fmt.Sprintf(" --(%s)", strings.Join(arr, " or "))) + } + + continue + } + flag := f.(string) + altName := altFlagName(flag) + if !viper.GetViper().IsSet(flag) && !viper.GetViper().IsSet(altName) { + unsetFlags = append(unsetFlags, fmt.Sprintf(" --%s", flag)) } } if len(unsetFlags) > 0 { diff --git a/internal/m2mauth/m2mauth.go b/internal/m2mauth/m2mauth.go index 31512eb..42f872c 100644 --- a/internal/m2mauth/m2mauth.go +++ b/internal/m2mauth/m2mauth.go @@ -27,6 +27,7 @@ import ( "io" "net/http" "net/url" + "os" "strings" "time" @@ -142,11 +143,23 @@ func (m *M2MAuthentication) awsAssumeRoleWithWebIdentity(at *okta.AccessToken) ( func (m *M2MAuthentication) createKeySigner() (jose.Signer, error) { signerOptions := (&jose.SignerOptions{}).WithHeader("kid", m.config.KeyID()) - priv := []byte(strings.ReplaceAll(m.config.PrivateKey(), `\n`, "\n")) + var priv []byte + switch { + case m.config.PrivateKey() != "": + priv = []byte(strings.ReplaceAll(m.config.PrivateKey(), `\n`, "\n")) + case m.config.PrivateKeyFile() != "": + var err error + priv, err = os.ReadFile(m.config.PrivateKeyFile()) + if err != nil { + return nil, err + } + default: + return nil, errors.New("either private key or private key file is a required m2m argument") + } privPem, _ := pem.Decode(priv) if privPem == nil { - return nil, errors.New("invalid private key") + return nil, errors.New("invalid private key value") } if privPem.Type == "RSA PRIVATE KEY" {