diff --git a/README.md b/README.md index 9aded5ab..0a65d182 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ KeyConjurer is made of three parts: - [cli](./cli/) - The CLI interface. - [frontend](./frontend/) - A static webpage which informs users on how to download and use KeyConjurer. -KeyConjurer is designed to work with Okta as an IdP, supports AWS and Tencent Cloud applications, and is inspired in part by [okta-aws-cli](https://github.com/okta/okta-aws-cli). The main difference from okta-aws-cli is that KeyConjurer does not require all users to have access to the Okta administration API - Instead, we use a Lambda function to access the protected resources required. +KeyConjurer is designed to work with Okta as an IdP, supports AWS applications, and is inspired in part by [okta-aws-cli](https://github.com/okta/okta-aws-cli). The main difference from okta-aws-cli is that KeyConjurer does not require all users to have access to the Okta administration API - Instead, we use a Lambda function to access the protected resources required. We use KeyConjurer a lot at Riot, but we can't guarantee any external support for this project. It's use at your own risk. If you encounter a bug or have a feature request, please feel free to raise a pull request or an issue against this repository. You're also welcome to fork the code and modify it as you see fit. @@ -48,7 +48,7 @@ In order to use KeyConjurer, an Okta administrator must configure their tenant a * Authorization Types: Hybrid Flow, Authorization Code, Token Exchange * Redirection URI: http://localhost:57468 * We recommend you enable Federated Mode on this native application so that users don't need to be explicitly assigned to it. -* All AWS and tencent applications must have their Allowed Web SSO Client set to the _Client ID_ of the native OIDC application that was created. This can be configured by going to the Sign On tab for each individual Okta application or managing the application configuration in an IAC provider, like Terraform. +* All AWS applications must have their Allowed Web SSO Client set to the _Client ID_ of the native OIDC application that was created. This can be configured by going to the Sign On tab for each individual Okta application or managing the application configuration in an IAC provider, like Terraform. Okta configuration should be configured _out of band_ and is not provided in this repository. diff --git a/cli/awsconfig.go b/cli/awsconfig.go index a26bec4c..8b01fc4e 100644 --- a/cli/awsconfig.go +++ b/cli/awsconfig.go @@ -3,7 +3,6 @@ package main import ( "os" "path/filepath" - "strings" "github.com/go-ini/ini" homedir "github.com/mitchellh/go-homedir" @@ -53,17 +52,11 @@ func ResolveAWSCredentialsPath(rootPath string) string { return rootPath } -func saveCredentialEntry(file *ini.File, entry CloudCliEntry, cloud string) error { +func saveCredentialEntry(file *ini.File, entry CloudCliEntry) error { section := file.Section(entry.profileName) - if cloud == cloudAws { - section.Key("aws_access_key_id").SetValue(entry.keyID) - section.Key("aws_secret_access_key").SetValue(entry.key) - section.Key("aws_session_token").SetValue(entry.token) - } else if cloud == cloudTencent { - section.Key("tencent_access_key_id").SetValue(entry.keyID) - section.Key("tencent_secret_access_key").SetValue(entry.key) - section.Key("tencent_session_token").SetValue(entry.token) - } + section.Key("aws_access_key_id").SetValue(entry.keyID) + section.Key("aws_secret_access_key").SetValue(entry.key) + section.Key("aws_session_token").SetValue(entry.token) return nil } @@ -74,12 +67,7 @@ func SaveCloudCredentialInCLI(cloudCliPath string, entry CloudCliEntry) error { return err } - cloud := cloudAws - if strings.Contains(strings.ToLower(path), cloudTencent) { - cloud = cloudTencent - } - - if err := saveCredentialEntry(file, entry, cloud); err != nil { + if err := saveCredentialEntry(file, entry); err != nil { return err } diff --git a/cli/awsconfig_test.go b/cli/awsconfig_test.go index 2e5ac322..d23213b1 100644 --- a/cli/awsconfig_test.go +++ b/cli/awsconfig_test.go @@ -20,7 +20,7 @@ func TestAddAWSCliEntry(t *testing.T) { token: "notatoken", } - require.NoError(t, saveCredentialEntry(file, entry, cloudAws)) + require.NoError(t, saveCredentialEntry(file, entry)) sec := file.Section("test-profile") require.NotNil(t, sec, "section should have been added above") diff --git a/cli/config.go b/cli/config.go index c2408d6e..841b00dd 100644 --- a/cli/config.go +++ b/cli/config.go @@ -31,7 +31,7 @@ type Account struct { } func (a *Account) NormalizeName() string { - magicPrefixes := []string{"AWS - ", "Tencent - "} + magicPrefixes := []string{"AWS - "} name := a.Name for _, prefix := range magicPrefixes { name = strings.TrimPrefix(name, prefix) @@ -62,9 +62,8 @@ type accountSet struct { accounts map[string]*Account } -// need support Aws and Tencent func generateDefaultAlias(name string) string { - magicPrefixes := []string{"AWS -", "Tencent -", "Tencent Cloud -"} + magicPrefixes := []string{"AWS -"} for _, prefix := range magicPrefixes { name = strings.TrimPrefix(name, prefix) name = strings.TrimSpace(name) diff --git a/cli/config_test.go b/cli/config_test.go index 4e371ae9..0f24099b 100644 --- a/cli/config_test.go +++ b/cli/config_test.go @@ -118,8 +118,6 @@ func TestAliasesPreservedAfterReplaceWith(t *testing.T) { func TestGeneratesGoodAliases(t *testing.T) { pairs := [][2]string{ - {"Tencent Cloud - Foo Bar", "foo-bar"}, - {"Tencent Cloud - Foobar", "foobar"}, {"AWS - Foo Bar", "foo-bar"}, {"AWS - Foobar", "foobar"}, } diff --git a/cli/credentials.go b/cli/credentials.go index b460c964..dccc943b 100644 --- a/cli/credentials.go +++ b/cli/credentials.go @@ -46,19 +46,6 @@ type CloudCredentials struct { SecretAccessKey string `json:"SecretAccessKey"` SessionToken string `json:"SessionToken"` Expiration string `json:"Expiration"` - - credentialsType string -} - -func LoadTencentCredentialsFromEnvironment() CloudCredentials { - return CloudCredentials{ - AccessKeyID: os.Getenv("TENCENTCLOUD_SECRET_ID"), - SecretAccessKey: os.Getenv("TENCENTCLOUD_SECRET_KEY"), - SessionToken: os.Getenv("TENCENTCLOUD_TOKEN"), - AccountID: os.Getenv("TENCENTKEY_ACCOUNT"), - Expiration: os.Getenv("TENCENTKEY_EXPIRATION"), - credentialsType: cloudTencent, - } } func LoadAWSCredentialsFromEnvironment() CloudCredentials { @@ -68,7 +55,6 @@ func LoadAWSCredentialsFromEnvironment() CloudCredentials { SessionToken: os.Getenv("AWS_SESSION_TOKEN"), AccountID: os.Getenv("AWSKEY_ACCOUNT"), Expiration: os.Getenv("AWSKEY_EXPIRATION"), - credentialsType: cloudAws, } } @@ -99,16 +85,6 @@ $Env:TF_VAR_secret_key = $Env:AWS_SECRET_ACCESS_KEY $Env:TF_VAR_token = $Env:AWS_SESSION_TOKEN $Env:AWSKEY_EXPIRATION = "%v" $Env:AWSKEY_ACCOUNT = "%v" -` - tencentShellTypePowershell = `$Env:TENCENTCLOUD_SECRET_ID = "%v" -$Env:TENCENTCLOUD_SECRET_KEY = "%v" -$Env:TENCENTCLOUD_TOKEN = "%v" -$Env:TENCENTCLOUD_SECURITY_TOKEN = "%v" -$Env:TF_VAR_access_key = $Env:TENCENTCLOUD_SECRET_ID -$Env:TF_VAR_secret_key = $Env:TENCENTCLOUD_SECRET_KEY -$Env:TF_VAR_token = $Env:TENCENTCLOUD_TOKEN -$Env:TENCENT_KEY_EXPIRATION = "%v" -$Env:TENCENT_KEY_ACCOUNT = "%v" ` awsShellTypeBasic = `SET AWS_ACCESS_KEY_ID=%v SET AWS_SECRET_ACCESS_KEY=%v @@ -120,15 +96,6 @@ SET TF_VAR_token=%%AWS_SESSION_TOKEN%% SET AWSKEY_EXPIRATION=%v SET AWSKEY_ACCOUNT=%v ` - tencentShellTypeBasic = `SET TENCENTCLOUD_SECRET_ID=%v -SET TENCENTCLOUD_SECRET_KEY=%v -SET TENCENTCLOUD_TOKEN=%v -SET TENCENTCLOUD_SECURITY_TOKEN=%v -SET TF_VAR_access_key=%%TENCENTCLOUD_SECRET_ID%% -SET TF_VAR_secret_key=%%TENCENTCLOUD_SECRET_KEY%% -SET TF_VAR_token=%%TENCENTCLOUD_TOKEN%% -SET TENCENTKEY_EXPIRATION=%v -SET TENCENTKEY_ACCOUNT=%v` awsShellTypeBash = `export AWS_ACCESS_KEY_ID=%v export AWS_SECRET_ACCESS_KEY=%v export AWS_SESSION_TOKEN=%v @@ -138,16 +105,6 @@ export TF_VAR_secret_key=$AWS_SECRET_ACCESS_KEY export TF_VAR_token=$AWS_SESSION_TOKEN export AWSKEY_EXPIRATION=%v export AWSKEY_ACCOUNT=%v -` - tencentShellTypeBash = `export TENCENTCLOUD_SECRET_ID=%v -export TENCENTCLOUD_SECRET_KEY=%v -export TENCENTCLOUD_TOKEN=%v -export TENCENT_SECURITY_TOKEN=%v -export TF_VAR_access_key=$TENCENTCLOUD_SECRET_ID -export TF_VAR_secret_key=$TENCENTCLOUD_SECRET_KEY -export TF_VAR_token=$TENCENTCLOUD_TOKEN -export TENCENTKEY_EXPIRATION=%v -export TENCENTKEY_ACCOUNT=%v ` ) @@ -160,19 +117,10 @@ func (c CloudCredentials) WriteFormat(w io.Writer, format ShellType) (int, error switch format { case shellTypePowershell: str = awsShellTypePowershell - if c.credentialsType == cloudTencent { - str = tencentShellTypePowershell - } case shellTypeBasic: str = awsShellTypeBasic - if c.credentialsType == cloudTencent { - str = tencentShellTypeBasic - } case shellTypeBash: str = awsShellTypeBash - if c.credentialsType == cloudTencent { - str = tencentShellTypeBash - } } return fmt.Fprintf(w, str, c.AccessKeyID, c.SecretAccessKey, c.SessionToken, c.SessionToken, c.Expiration, c.AccountID) diff --git a/cli/get.go b/cli/get.go index f5052d8a..c341e606 100644 --- a/cli/get.go +++ b/cli/get.go @@ -12,12 +12,16 @@ import ( ) var ( - FlagRegion = "region" - FlagRoleName = "role" - FlagTimeRemaining = "time-remaining" - FlagTimeToLive = "ttl" - FlagBypassCache = "bypass-cache" - FlagLogin = "login" + FlagRegion = "region" + FlagRoleName = "role" + FlagTimeRemaining = "time-remaining" + FlagTimeToLive = "ttl" + FlagBypassCache = "bypass-cache" + FlagLogin = "login" + FlagShellType = "shell" + FlagOutputType = "out" + FlagRoleSessionName = "role-session-name" + FlagAWSCLIPath = "awscli" ) var ( @@ -25,10 +29,8 @@ var ( outputTypeEnvironmentVariable = "env" // outputTypeAWSCredentialsFile indicates that keyconjurer will dump the credentials into the ~/.aws/credentials file. outputTypeAWSCredentialsFile = "awscli" - // outputTypeTencentCredentialsFile indicates that keyconjurer will dump the credentials into the ~/.tencent/credentials file. - outputTypeTencentCredentialsFile = "tencentcli" - permittedOutputTypes = []string{outputTypeAWSCredentialsFile, outputTypeEnvironmentVariable, outputTypeTencentCredentialsFile} - permittedShellTypes = []string{shellTypePowershell, shellTypeBash, shellTypeBasic, shellTypeInfer} + permittedOutputTypes = []string{outputTypeAWSCredentialsFile, outputTypeEnvironmentVariable} + permittedShellTypes = []string{shellTypePowershell, shellTypeBash, shellTypeBasic, shellTypeInfer} ) func init() { @@ -37,13 +39,11 @@ func init() { getCmd.Flags().UintP(FlagTimeRemaining, "t", DefaultTimeRemaining, "Request new keys if there are no keys in the environment or the current keys expire within minutes. Defaults to 60.") getCmd.Flags().StringP(FlagRoleName, "r", "", "The name of the role to assume.") getCmd.Flags().String(FlagRoleSessionName, "KeyConjurer-AssumeRole", "the name of the role session name that will show up in CloudTrail logs") - getCmd.Flags().StringP(FlagOutputType, "o", outputTypeEnvironmentVariable, "Format to save new credentials in. Supported outputs: env, awscli,tencentcli") + getCmd.Flags().StringP(FlagOutputType, "o", outputTypeEnvironmentVariable, "Format to save new credentials in. Supported outputs: env, awscli") getCmd.Flags().String(FlagShellType, shellTypeInfer, "If output type is env, determines which format to output credentials in - by default, the format is inferred based on the execution environment. WSL users may wish to overwrite this to `bash`") - getCmd.Flags().String(FlagAWSCLIPath, "~/.aws/", "Path for directory used by the aws-cli tool. Default is \"~/.aws\".") - getCmd.Flags().String(FlagTencentCLIPath, "~/.tencent/", "Path for directory used by the tencent-cli tool. Default is \"~/.tencent\".") - getCmd.Flags().String(FlagCloudType, "aws", "Choose a cloud vendor. Default is aws. Can choose aws or tencent") getCmd.Flags().Bool(FlagBypassCache, false, "Do not check the cache for accounts and send the application ID as-is to Okta. This is useful if you have an ID you know is an Okta application ID and it is not stored in your local account cache.") getCmd.Flags().Bool(FlagLogin, false, "Login to Okta before running the command") + getCmd.Flags().String(FlagAWSCLIPath, "~/.aws/", "Path for directory used by the aws CLI") } func isMemberOfSlice(slice []string, val string) bool { @@ -95,9 +95,7 @@ A role must be specified when using this command through the --role flag. You ma outputType, _ := cmd.Flags().GetString(FlagOutputType) shellType, _ := cmd.Flags().GetString(FlagShellType) roleName, _ := cmd.Flags().GetString(FlagRoleName) - cloudType, _ := cmd.Flags().GetString(FlagCloudType) awsCliPath, _ := cmd.Flags().GetString(FlagAWSCLIPath) - tencentCliPath, _ := cmd.Flags().GetString(FlagTencentCLIPath) if !isMemberOfSlice(permittedOutputTypes, outputType) { return ValueError{Value: outputType, ValidValues: permittedOutputTypes} @@ -135,15 +133,9 @@ A role must be specified when using this command through the --role flag. You ma timeRemaining = config.TimeRemaining } - var credentials CloudCredentials - if cloudType == cloudAws { - credentials = LoadAWSCredentialsFromEnvironment() - } else if cloudType == cloudTencent { - credentials = LoadTencentCredentialsFromEnvironment() - } - + credentials := LoadAWSCredentialsFromEnvironment() if credentials.ValidUntil(account, time.Duration(timeRemaining)*time.Minute) { - return echoCredentials(accountID, accountID, credentials, outputType, shellType, awsCliPath, tencentCliPath) + return echoCredentials(accountID, accountID, credentials, outputType, shellType, awsCliPath) } samlResponse, assertionStr, err := DiscoverConfigAndExchangeTokenForAssertion(cmd.Context(), NewHTTPClient(), config.Tokens, oidcDomain, clientID, account.ID) @@ -160,38 +152,33 @@ A role must be specified when using this command through the --role flag. You ma ttl = config.TTL } - if cloudType == cloudAws { - region, _ := cmd.Flags().GetString(FlagRegion) - session, _ := session.NewSession(&aws.Config{Region: aws.String(region)}) - stsClient := sts.New(session) - timeoutInSeconds := int64(3600 * ttl) - resp, err := stsClient.AssumeRoleWithSAMLWithContext(ctx, &sts.AssumeRoleWithSAMLInput{ - DurationSeconds: &timeoutInSeconds, - PrincipalArn: &pair.ProviderARN, - RoleArn: &pair.RoleARN, - SAMLAssertion: &assertionStr, - }) - - if err, ok := tryParseTimeToLiveError(err); ok { - return err - } + region, _ := cmd.Flags().GetString(FlagRegion) + session, _ := session.NewSession(&aws.Config{Region: aws.String(region)}) + stsClient := sts.New(session) + timeoutInSeconds := int64(3600 * ttl) + resp, err := stsClient.AssumeRoleWithSAMLWithContext(ctx, &sts.AssumeRoleWithSAMLInput{ + DurationSeconds: &timeoutInSeconds, + PrincipalArn: &pair.ProviderARN, + RoleArn: &pair.RoleARN, + SAMLAssertion: &assertionStr, + }) - if err != nil { - return AWSError{ - InnerError: err, - Message: "failed to exchange credentials", - } - } + if err, ok := tryParseTimeToLiveError(err); ok { + return err + } - credentials = CloudCredentials{ - AccessKeyID: *resp.Credentials.AccessKeyId, - Expiration: resp.Credentials.Expiration.Format(time.RFC3339), - SecretAccessKey: *resp.Credentials.SecretAccessKey, - SessionToken: *resp.Credentials.SessionToken, - credentialsType: cloudType, + if err != nil { + return AWSError{ + InnerError: err, + Message: "failed to exchange credentials", } - } else { - panic("not yet implemented") + } + + credentials = CloudCredentials{ + AccessKeyID: *resp.Credentials.AccessKeyId, + Expiration: resp.Credentials.Expiration.Format(time.RFC3339), + SecretAccessKey: *resp.Credentials.SecretAccessKey, + SessionToken: *resp.Credentials.SessionToken, } if account != nil { @@ -199,22 +186,18 @@ A role must be specified when using this command through the --role flag. You ma } config.LastUsedAccount = &accountID - return echoCredentials(accountID, accountID, credentials, outputType, shellType, awsCliPath, tencentCliPath) + return echoCredentials(accountID, accountID, credentials, outputType, shellType, awsCliPath) }} -func echoCredentials(id, name string, credentials CloudCredentials, outputType, shellType, awsCliPath, tencentCliPath string) error { +func echoCredentials(id, name string, credentials CloudCredentials, outputType, shellType, awsCliPath string) error { switch outputType { case outputTypeEnvironmentVariable: credentials.WriteFormat(os.Stdout, shellType) return nil - case outputTypeAWSCredentialsFile, outputTypeTencentCredentialsFile: + case outputTypeAWSCredentialsFile: acc := Account{ID: id, Name: name} newCliEntry := NewCloudCliEntry(credentials, &acc) - cliPath := awsCliPath - if outputType == outputTypeTencentCredentialsFile { - cliPath = tencentCliPath - } - return SaveCloudCredentialInCLI(cliPath, newCliEntry) + return SaveCloudCredentialInCLI(awsCliPath, newCliEntry) default: return fmt.Errorf("%s is an invalid output type", outputType) } diff --git a/cli/root.go b/cli/root.go index 07838e85..fb31ba23 100644 --- a/cli/root.go +++ b/cli/root.go @@ -18,8 +18,6 @@ var ( FlagConfigPath = "config" FlagQuiet = "quiet" FlagTimeout = "timeout" - cloudAws = "aws" - cloudTencent = "tencent" ) func init() { @@ -33,7 +31,6 @@ func init() { rootCmd.AddCommand(getCmd) rootCmd.AddCommand(setCmd) rootCmd.AddCommand(upgradeCmd) - rootCmd.AddCommand(&switchCmd) rootCmd.AddCommand(&aliasCmd) rootCmd.AddCommand(&unaliasCmd) rootCmd.AddCommand(&rolesCmd) diff --git a/cli/saml.go b/cli/saml.go index 575e980e..53ff6967 100644 --- a/cli/saml.go +++ b/cli/saml.go @@ -11,27 +11,16 @@ type RoleProviderPair struct { ProviderARN string } -const ( - awsFlag = 0 - tencentFlag = 1 -) - func ListSAMLRoles(response *saml.Response) []string { if response == nil { return nil } roleURL := "https://aws.amazon.com/SAML/Attributes/Role" - roleSubstr := "role/" - if response.GetAttribute(roleURL) == "" { - roleURL = "https://cloud.tencent.com/SAML/Attributes/Role" - roleSubstr = "roleName/" - } - var names []string for _, v := range response.GetAttributeValues(roleURL) { p := getARN(v) - idx := strings.Index(p.RoleARN, roleSubstr) + idx := strings.Index(p.RoleARN, "role/") parts := strings.Split(p.RoleARN[idx:], "/") names = append(names, parts[1]) } @@ -40,21 +29,16 @@ func ListSAMLRoles(response *saml.Response) []string { } func FindRoleInSAML(roleName string, response *saml.Response) (RoleProviderPair, bool) { + var rpp RoleProviderPair if response == nil { - return RoleProviderPair{}, false + return rpp, false } roleURL := "https://aws.amazon.com/SAML/Attributes/Role" roleSubstr := "role/" attrs := response.GetAttributeValues(roleURL) if len(attrs) == 0 { - attrs = response.GetAttributeValues("https://cloud.tencent.com/SAML/Attributes/Role") - roleSubstr = "roleName/" - } - - if len(attrs) == 0 { - // The SAML assertoin contains no known roles for AWS or Tencent. - return RoleProviderPair{}, false + return rpp, false } var pairs []RoleProviderPair @@ -63,7 +47,7 @@ func FindRoleInSAML(roleName string, response *saml.Response) (RoleProviderPair, } if len(pairs) == 0 { - return RoleProviderPair{}, false + return rpp, false } var pair RoleProviderPair @@ -76,7 +60,7 @@ func FindRoleInSAML(roleName string, response *saml.Response) (RoleProviderPair, } if pair.RoleARN == "" { - return RoleProviderPair{}, false + return rpp, false } return pair, true diff --git a/cli/switch.go b/cli/switch.go deleted file mode 100644 index 1f817518..00000000 --- a/cli/switch.go +++ /dev/null @@ -1,190 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - "strings" - "time" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/arn" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/sts" - "github.com/riotgames/key-conjurer/internal/tencent" - "github.com/spf13/cobra" -) - -var ( - FlagRoleSessionName = "role-session-name" - FlagOutputType = "out" - FlagShellType = "shell" - FlagAWSCLIPath = "awscli" - FlagTencentCLIPath = "tencentcli" - FlagCloudType = "cloud" -) - -func init() { - switchCmd.Flags().String(FlagRoleSessionName, "KeyConjurer-AssumeRole", "the name of the role session name that will show up in CloudTrail logs") - switchCmd.Flags().StringP(FlagOutputType, "o", outputTypeEnvironmentVariable, "Format to save new credentials in. Supported outputs: env, awscli,tencentcli") - switchCmd.Flags().String(FlagShellType, shellTypeInfer, "If output type is env, determines which format to output credentials in - by default, the format is inferred based on the execution environment. WSL users may wish to overwrite this to `bash`") - switchCmd.Flags().String(FlagAWSCLIPath, "~/.aws/", "Path for directory used by the aws-cli tool. Default is \"~/.aws\".") - switchCmd.Flags().String(FlagTencentCLIPath, "~/.tencent/", "Path for directory used by the tencent-cli tool. Default is \"~/.tencent\".") - switchCmd.Flags().String(FlagCloudType, "aws", "Choose a cloud vendor. Default is aws. Can choose aws or tencent") -} - -var switchCmd = cobra.Command{ - Use: "switch ", - Short: "Switch from the current Cloud (AWS or Tencent) account into the one with the given Account ID.", - Long: `Attempt to AssumeRole into the given Cloud (AWS or Tencent) with the current credentials. You only need to use this if you are a power user or network engineer with access to many accounts. - -This is used when a "bastion" account exists which users initially authenticate into and then pivot from that account into other accounts. - -This command will fail if you do not have active Cloud credentials. -`, - Example: "keyconjurer switch 123456798", - Args: cobra.ExactArgs(1), - Aliases: []string{"switch-account"}, - RunE: func(cmd *cobra.Command, args []string) error { - outputType, _ := cmd.Flags().GetString(FlagOutputType) - shellType, _ := cmd.Flags().GetString(FlagShellType) - cloudType, _ := cmd.Flags().GetString(FlagCloudType) - awsCliPath, _ := cmd.Flags().GetString(FlagAWSCLIPath) - if !isMemberOfSlice(permittedOutputTypes, outputType) { - return ValueError{Value: outputType, ValidValues: permittedOutputTypes} - } - - if !isMemberOfSlice(permittedShellTypes, shellType) { - return ValueError{Value: shellType, ValidValues: permittedShellTypes} - } - - // We could read the environment variable for the assumed role ARN, but it might be expired which isn't very useful to the user. - var err error - var creds CloudCredentials - sessionName, _ := cmd.Flags().GetString(FlagRoleSessionName) - switch strings.ToLower(cloudType) { - case cloudAws: - creds, err = getAWSCredentials(args[0], sessionName) - case cloudTencent: - creds, err = getTencentCredentials(args[0], sessionName) - } - - if err != nil { - // If this failed, either there was a network error or the user is not authorized to assume into this role - // This can happen if the user is not authenticated using the Bastion instance. - return err - } - - switch outputType { - case outputTypeEnvironmentVariable: - creds.WriteFormat(os.Stdout, shellType) - return nil - case outputTypeAWSCredentialsFile: - acc := Account{ID: args[0], Name: args[0]} - newCliEntry := NewCloudCliEntry(creds, &acc) - return SaveCloudCredentialInCLI(awsCliPath, newCliEntry) - default: - return fmt.Errorf("%s is an invalid output type", outputType) - } - }, -} - -func getTencentCredentials(accountID, roleSessionName string) (creds CloudCredentials, err error) { - region := os.Getenv("TENCENT_REGION") - stsClient, err := tencent.NewSTSClient(region) - if err != nil { - return - } - - response, err := stsClient.GetCallerIdentity() - if err != nil { - return - } - - arn := response.Response.Arn - roleID := "" - if (*arn) != "" { - arns := strings.Split(*arn, ":") - if len(arns) >= 5 && len(strings.Split(arns[4], "/")) >= 2 { - roleID = strings.Split(arns[4], "/")[1] - } - } - if roleID == "" { - err = fmt.Errorf("roleID is null") - return - } - - camClient, err := tencent.NewCAMClient(region) - if err != nil { - return - } - roleName, err := camClient.GetRoleName(roleID) - if err != nil { - return - } - resp, err := stsClient.AssumeRole(fmt.Sprintf("qcs::cam::uin/%s:roleName/%s", accountID, roleName), roleSessionName) - if err != nil { - return - } - - creds = CloudCredentials{ - AccountID: accountID, - AccessKeyID: *resp.Response.Credentials.TmpSecretId, - SecretAccessKey: *resp.Response.Credentials.TmpSecretKey, - SessionToken: *resp.Response.Credentials.Token, - Expiration: *resp.Response.Expiration, - credentialsType: cloudTencent, - } - - return creds, nil -} - -func getAWSCredentials(accountID, roleSessionName string) (creds CloudCredentials, err error) { - ctx := context.Background() - sess, err := session.NewSession(aws.NewConfig()) - if err != nil { - return - } - - c := sts.New(sess) - response, err := c.GetCallerIdentityWithContext(ctx, &sts.GetCallerIdentityInput{}) - if err != nil { - return - } - - // We need to modify this to change the last section to be role/GL-SuperAdmin - id, err := arn.Parse(*response.Arn) - if err != nil { - return - } - - parts := strings.Split(id.Resource, "/") - arn := arn.ARN{ - AccountID: accountID, - Partition: "aws", - Service: "iam", - Resource: fmt.Sprintf("role/%s", parts[1]), - Region: id.Region, - } - - roleARN := arn.String() - resp, err := c.AssumeRoleWithContext(ctx, &sts.AssumeRoleInput{ - RoleArn: &roleARN, - RoleSessionName: &roleSessionName, - }) - - if err != nil { - return - } - - creds = CloudCredentials{ - AccountID: accountID, - AccessKeyID: *resp.Credentials.AccessKeyId, - SecretAccessKey: *resp.Credentials.SecretAccessKey, - SessionToken: *resp.Credentials.SessionToken, - Expiration: resp.Credentials.Expiration.Format(time.RFC3339), - credentialsType: cloudAws, - } - - return creds, nil -} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index f5503bdc..e6a979f0 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -15,7 +15,7 @@ export const App = () => (

KeyConjurer is an application for generating temporary session - credentials for AWS and Tencent Cloud. + credentials for AWS.

diff --git a/go.mod b/go.mod index cb2deed1..53c28f80 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ require ( github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b github.com/aws/aws-lambda-go v1.19.1 github.com/aws/aws-sdk-go v1.34.19 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.148.2 github.com/coreos/go-oidc v2.2.1+incompatible github.com/go-ini/ini v1.61.0 github.com/hashicorp/go-rootcerts v1.0.2 @@ -16,16 +15,12 @@ require ( github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.1 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam v1.0.392 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.479 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sts v1.0.479 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 golang.org/x/net v0.8.0 golang.org/x/oauth2 v0.6.0 ) require ( - github.com/aws/smithy-go v1.20.1 // indirect github.com/cenkalti/backoff/v4 v4.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.5.2 // indirect diff --git a/go.sum b/go.sum index f9aecef1..3f9d11c7 100644 --- a/go.sum +++ b/go.sum @@ -10,11 +10,6 @@ github.com/aws/aws-lambda-go v1.19.1/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XO github.com/aws/aws-sdk-go v1.34.10/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.34.19 h1:x3MMvAJ1nfWviixEduchBSs65DgY5Y2pA2/NAcxVGOo= github.com/aws/aws-sdk-go v1.34.19/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go-v2 v1.25.1 h1:P7hU6A5qEdmajGwvae/zDkOq+ULLC9tQBTwqqiwFGpI= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.148.2 h1:1oOlVyfM5Lzn/XKjqoVyy2i4OQhqOPaqYg3Jk+cZ4FE= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.148.2/go.mod h1:7MUTgVVnC1GAxx4SNQqzQalrm1n4v1HYa/R/LEB3CKo= -github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= -github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc= github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= @@ -174,13 +169,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam v1.0.392 h1:L5BT7Fan1nMh2HQaXrhSHGULk/0/mKxRsXts/KAj1xA= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam v1.0.392/go.mod h1:acBn3ulGGf5qxMPOEZhBHk6j5APeHaY+6fNXSzRpdss= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.392/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.479 h1:3kwDb6p1J3LxmwnNgSSEheemPffo+vMewoDzKysYdig= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.479/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sts v1.0.479 h1:z5nPMk1fsBtv/Lg1dxZh5iqG9F0UYkx36ruDQzoAWnA= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sts v1.0.479/go.mod h1:pf2/MFFmMbHRQnKC7/vXhN4aJQU/nGpu7h3uwed//lw= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/internal/api/serverless_functions.go b/internal/api/serverless_functions.go index a999c790..4ca16639 100644 --- a/internal/api/serverless_functions.go +++ b/internal/api/serverless_functions.go @@ -4,7 +4,6 @@ import ( "context" "errors" "net/http" - "strings" "github.com/okta/okta-sdk-golang/v2/okta" "golang.org/x/exp/slog" @@ -57,7 +56,7 @@ func ServeUserApplications(okta OktaService) http.Handler { var accounts []Application for _, app := range applications { - if app.AppName == "amazon_aws" || strings.Contains(app.AppName, "tencent") { + if app.AppName == "amazon_aws" { accounts = append(accounts, Application{ ID: app.AppInstanceId, Name: app.Label, diff --git a/internal/aws/provider.go b/internal/aws/provider.go deleted file mode 100644 index 6538f789..00000000 --- a/internal/aws/provider.go +++ /dev/null @@ -1,37 +0,0 @@ -package aws - -import ( - "context" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/sts" -) - -type Provider struct { - stsClient *sts.STS -} - -func NewProvider(region string) (*Provider, error) { - config, err := session.NewSession(&aws.Config{Region: aws.String(region)}) - if err != nil { - return nil, err - } - return &Provider{stsClient: sts.New(config)}, nil -} - -func (p *Provider) GetTemporaryCredentialsForUser(ctx context.Context, principalARN, roleARN, sAMLAssertion *string, ttlInHours int) (*sts.Credentials, error) { - timeoutInSeconds := int64(3600 * ttlInHours) - resp, err := p.stsClient.AssumeRoleWithSAMLWithContext(ctx, &sts.AssumeRoleWithSAMLInput{ - DurationSeconds: &timeoutInSeconds, - PrincipalArn: principalARN, - RoleArn: roleARN, - SAMLAssertion: sAMLAssertion, - }) - - if err != nil { - return nil, err - } - - return resp.Credentials, nil -} diff --git a/internal/tencent/provider.go b/internal/tencent/provider.go deleted file mode 100644 index 719adf1c..00000000 --- a/internal/tencent/provider.go +++ /dev/null @@ -1,156 +0,0 @@ -package tencent - -import ( - "context" - "fmt" - "os" - - cam "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam/v20190116" - "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" - tcerr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" - "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" - "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/regions" - sts "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sts/v20180813" -) - -type Provider struct { - stsClient *sts.Client -} - -func NewProvider(region string) (*Provider, error) { - cpf := profile.NewClientProfile() - cpf.HttpProfile.Endpoint = "sts.tencentcloudapi.com" - client, _ := sts.NewClient(&common.Credential{}, region, cpf) - return &Provider{stsClient: client}, nil -} - -func (p *Provider) GetTemporaryCredentialsForUser(ctx context.Context, principalARN, roleARN, sAMLAssertion *string, ttlInHours int, roleName string) (*sts.Credentials, *string, error) { - timeoutInSeconds := int64(3600 * ttlInHours) - req := sts.NewAssumeRoleWithSAMLRequest() - req.RoleSessionName = common.StringPtr(fmt.Sprintf("riot-keyConjurer-%s", roleName)) - req.DurationSeconds = common.Uint64Ptr(uint64(timeoutInSeconds)) - req.PrincipalArn = principalARN - req.RoleArn = roleARN - req.SAMLAssertion = sAMLAssertion - resp, err := p.stsClient.AssumeRoleWithSAMLWithContext(ctx, req) - if err != nil { - return nil, nil, err - } - - return resp.Response.Credentials, resp.Response.Expiration, nil -} - -// STS Client -type STSClient struct { - client *sts.Client -} - -// init New STS Client -func NewSTSClient(region string) (*STSClient, error) { - creds, err := ChainedCredsToCli() - if err != nil { - return nil, err - } - profile := profile.NewClientProfile() - profile.Language = "en-US" - profile.HttpProfile.ReqTimeout = 90 - profile.HttpProfile.Endpoint = "sts.tencentcloudapi.com" - if region == "" { - region = regions.SiliconValley - } - client, err := sts.NewClient(creds, region, profile) - if err != nil { - return nil, err - } - return &STSClient{client: client}, nil -} - -func (c *STSClient) GetCallerIdentity() (*sts.GetCallerIdentityResponse, error) { - return c.client.GetCallerIdentity(sts.NewGetCallerIdentityRequest()) -} -func (c *STSClient) AssumeRole(roleARN, roleSessionName string) (*sts.AssumeRoleResponse, error) { - request := sts.NewAssumeRoleRequest() - request.RoleArn = &roleARN - request.RoleSessionName = &roleSessionName - return c.client.AssumeRole(request) -} - -// CAM Client -type CAMClient struct { - client *cam.Client -} - -// init New CAM Client -func NewCAMClient(region string) (*CAMClient, error) { - creds, err := ChainedCredsToCli() - if err != nil { - return nil, err - } - profile := profile.NewClientProfile() - profile.Language = "en-US" - profile.HttpProfile.ReqTimeout = 90 - if region == "" { - region = regions.SiliconValley - } - client, err := cam.NewClient(creds, region, profile) - if err != nil { - return nil, err - } - return &CAMClient{client: client}, nil -} - -// APIļ¼š GetRoleName -func (c *CAMClient) GetRoleName(roleID string) (roleName string, err error) { - req := cam.NewGetRoleRequest() - req.RoleId = &roleID - roleRsp, err := c.client.GetRole(req) - fmt.Println(roleRsp.ToJsonString()) - if err != nil { - return "", err - } - return *(roleRsp.Response.RoleInfo.RoleName), nil -} - -// client chainedCreds for Cli -func ChainedCredsToCli() (common.CredentialIface, error) { - providerChain := []common.Provider{ - DefaultEnvProvider(), - } - return common.NewProviderChain(providerChain).GetCredential() -} - -// for tools login to STS auth -type EnvProvider struct { - secretID string - secretKey string - token string -} - -// DefaultEnvProvider return a default provider -// The default environment variable name are TENCENTCLOUD_SECRET_ID and TENCENTCLOUD_SECRET_KEY and TOKEN -func DefaultEnvProvider() *EnvProvider { - return &EnvProvider{ - secretID: "TENCENTCLOUD_SECRET_ID", - secretKey: "TENCENTCLOUD_SECRET_KEY", - token: "TENCENTCLOUD_TOKEN", - } -} - -// GetCredential -func (p *EnvProvider) GetCredential() (common.CredentialIface, error) { - secretID, ok1 := os.LookupEnv(p.secretID) - secretKey, ok2 := os.LookupEnv(p.secretKey) - token, ok3 := os.LookupEnv(p.token) - if !ok1 || !ok2 || !ok3 { - return nil, envNotSet - } - if secretID == "" || secretKey == "" || token == "" { - return nil, tcerr.NewTencentCloudSDKError(creErr, - "Environmental variable ("+p.secretID+" or "+ - p.secretKey+" or "+p.secretKey+") is empty", "") - } - return common.NewTokenCredential(secretID, secretKey, token), nil -} - -var creErr = "ClientError.CredentialError" -var envNotSet = tcerr.NewTencentCloudSDKError(creErr, "could not find environmental variable", "")