Added TOML cofiguration file support

* configuration file located at ~/.aws-oidc/config
 * sets default parameters, but can still be overridden on the cli
 * named AuthProviders are accessible via the auth [name] command
Renamed exec command to auth. Upgraded auth command to take defaults from the config file.
Added new command exec, that puts the temporary credentials as environment variables in the specified command
Automatically append URL to end of auth command if not specified
This commit is contained in:
Jeremy Stott 2019-04-24 15:34:01 +12:00
parent c548dcfd72
commit f8a7c0986f
10 changed files with 425 additions and 191 deletions

View File

@ -6,58 +6,69 @@ It outputs temporary AWS credentials in a JSON format that can be consumed by th
https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes
OneLogin Example:
Example:
aws-oidc exec \
--role_arn="arn:aws:iam::892845094662:role/onelogin-test-oidc" \
--provider_url=https://openid-connect.onelogin.com/oidc \
--client_id=97a61160-3c09-0137-8c69-0a1c3f4fd822144813 \
--pkce \
--nonce \
-- open -b com.google.chrome -n --args --profile-directory=Default {}
aws-oidc auth \
--role_arn="arn:aws:iam::892845094662:role/onelogin-test-oidc" \
--duration=3600 \
--provider_url=https://openid-connect.onelogin.com/oidc \
--client_id=97a61160-3c09-0137-8c69-0a1c3f4fd822144813 \
--agent=open
Google Example:
All the provider arguments can be specified in a TOML configuration file:
aws-oidc exec \
--role_arn="arn:aws:iam::892845094662:role/web-identity-lanbda" \
--provider_url=https://accounts.google.com \
--client_id=430784603061-osbtei3s71l0bj6d8oegto0itefjmiq6.apps.googleusercontent.com \
--client_secret=... \
--pkce \
--nonce \
-- open -b com.google.chrome -n --args --profile-directory=Default {}
region = "ap-southeast-2"
For some reason, even when using PKCE, google need a client_secret for applications not registered as Android, iOS or Chrome.
[[AuthProvider]]
name = "onelogin"
role_arn = "arn:aws:iam::012345678901:role/role-name"
duration = 900
provider_url = "https://openid-connect.onelogin.com/oidc"
client_id = "ef061080-43aa-0137-62f3-066d8813aeb888900"
agent = ["open", "-b", "com.google.chrome", "-n", "--args", "--profile-directory=Default", "{}"]
[[AuthProvider]]
name = "google"
role_arn = "arn:aws:iam::012345678901:role/role-name"
duration = 900
provider_url = "https://accounts.google.com"
client_id = "430784603061-osbtei3s71l0bj6d8oegto0itefjmiq6.apps.googleusercontent.com"
agent = ["open", "-b", "com.google.chrome", "-n", "--args", "--profile-directory=Profile 1", "{}"]
This configuration file should be located in **~/.aws-oidc/config**
## Configure AWS Config
~/.aws/config
Add the profiles for each role you want to assume to **~/.aws/config**. Specify the provider name from the configuration file, and override any default settings:
[profile oidc]
credential_process = aws-oidc exec --role_arn=arn:aws:iam::892845094662:role/onelogin-test-oidc --provider_url=https://openid-connect.onelogin.com/oidc --client_id=97a61160-3c09-0137-8c69-0a1c3f4fd822144813 --pkce --nonce -- open -b com.google.chrome -n --args --profile-directory=Default {}
[profile engineer]
credential_process = aws-oidc auth onelogin --role_arn=arn:aws:iam::892845094662:role/onelogin-test-oidc --duration 7200
Now you can use the AWS cli as normal, and specify the profile:
$ aws --profile oidc sts get-caller-identity
$ aws --profile engineer sts get-caller-identity
{
"UserId": "AROAJUTXNWXGCAEILMXTY:50904038",
"Account": "892845094662",
"Arn": "arn:aws:sts::892845094662:assumed-role/onelogin-test-oidc/50904038"
}
## AWS Cognito
## Run other commands with credentials
./aws-oidc exec \
--provider_url=https://cognito-idp.us-west-2.amazonaws.com/us-west-2_eBYNmnpS9 \
--client_id=70kdnvprlqf1daspkn0iikdngv \
--pkce \
--nonce \
--no-reauth \
-- open -b com.google.chrome -n --args --profile-directory=Default {}
Most AWS SDK's should be able to pick up the profile parameter, and suppor the `credentials_process` setting in your **~/.aws/config** file. If not, you can run an arbitary command with the temporary credentials with `exec`:
aws-oidc exec engineer -- ./path/to/command with arguments
This will use the profiles defined in **~/.aws/config** to assume the role with `aws-oidc` and then set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables for the new process.
## Find roles that an oidc client could assume
aws-vault exec test-privileged-admin -- aws iam list-roles --query <<EOF '
Use the `list` command to find roles that your claim and client_id can assume:
aws-oidc list --claim="openid-connect.onelogin.com/oidc:aud" --client_id="ef061080-43aa-0137-62f3-066d8813aeb888900"
Example using only the AWS CLI:
aws iam list-roles --query <<EOF '
Roles[?
AssumeRolePolicyDocument.Statement[?
Condition.StringEquals."openid-connect.onelogin.com/oidc:aud"
@ -69,4 +80,4 @@ Now you can use the AWS cli as normal, and specify the profile:
} | [?
contains(ClientId, `ef061080-43aa-0137-62f3-066d8813aeb888900`)
]'
EOF
EOF

View File

@ -1,16 +1,17 @@
package main
import (
"io"
"log"
"os"
"github.com/BurntSushi/toml"
"github.com/stoggi/aws-oidc/cli"
"gopkg.in/alecthomas/kingpin.v2"
)
// Version is provided at compile time
var Version = "dev"
var labelText chan string
func main() {
run(os.Args[1:], os.Exit)
@ -23,12 +24,16 @@ func run(args []string, exit func(int)) {
log.Fatalf("error opening file: %v", err)
}
defer f.Close()
log.SetOutput(f)
log.Println("Starting...")
wrt := io.MultiWriter(os.Stderr, f)
log.SetOutput(wrt)
// Default configuration, values are overridden by command line options.
config := cli.GlobalConfig{}
if _, err := toml.DecodeFile(GetConfigFilePath(), &config); err != nil {
if !os.IsNotExist(err) {
log.Printf("Error decoding TOML: %v\n", err)
}
}
app := kingpin.New(
"aws-oidc",
@ -38,9 +43,10 @@ func run(args []string, exit func(int)) {
app.Version(Version)
app.Terminate(exit)
app.UsageWriter(os.Stdout)
app.ErrorWriter(f)
app.ErrorWriter(wrt)
cli.ConfigureGlobal(app, &config)
cli.ConfigureAuth(app, &config)
cli.ConfigureExec(app, &config)
cli.ConfigureList(app, &config)

211
cli/auth.go Normal file
View File

@ -0,0 +1,211 @@
package cli
import (
"encoding/json"
"fmt"
"net/url"
"strconv"
"github.com/99designs/keyring"
"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/stoggi/aws-oidc/provider"
"gopkg.in/alecthomas/kingpin.v2"
)
// AuthConfig defines a single OpenIDConnect provider
type AuthConfig struct {
// The name of the provider when definied in the TOML configuration file
Name string `toml:"name"`
// RoleARN the role in AWS that should be assumed with the identity token
RoleArn string `toml:"role_arn"`
// Duration in seconds that the temporary AWS credentials should last for
// Between 900 (15 minutes) and 43200 (12 hours)
Duration int64 `toml:"duration"`
// ProviderURL the endpoint that defines the OIDC provider.
// Should serve https://[ProviderURL]/.well-known/openid-configuration
ProviderURL string `toml:"provider_url"`
// ClientID configured with your OIDC provider
ClientID string `toml:"client_id"`
// ClientSecret should only be specified if your OIDC provider requires it.
// Normally with PKCE you don't require a client_secret.
ClientSecret string `toml:"client_secret"`
// DisablePKCE removes the code_challenge and code_verifier parameters of a
// proof key for code exchange OAuth flow. Only disbale this if your identity
// provider does not support PKCE.
DisablePKCE bool `toml:"disable_pkce"`
// DisableNonce removes a random nonce sent to the server, and added to the token
// This nonce is verified when the token is received by the command line app.
DisableNonce bool `toml:"disable_nonce"`
// AgentCommand contains the command and arguments that open a browser. The URL
// to be opened will be appended, or use a parameter of {} to substitute the URL.
AgentCommand []string `toml:"agent"`
}
// AwsCredentialHelperData for AWS credential process
// https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes
type AwsCredentialHelperData struct {
Version int `json:"Version"`
AccessKeyID string `json:"AccessKeyId"`
SecretAccessKey string `json:"SecretAccessKey"`
SessionToken string `json:"SessionToken"`
Expiration string `json:"Expiration,omitempty"`
}
func configureFlags(cmd *kingpin.CmdClause, authConfig *AuthConfig) {
cmd.Flag("role_arn", "The AWS role you want to assume").
Default(authConfig.RoleArn).
StringVar(&authConfig.RoleArn)
cmd.Flag("duration", "The duration to assume the role for in seconds").
Default(strconv.FormatInt(max(authConfig.Duration, 900), 10)).
Int64Var(&authConfig.Duration)
cmd.Flag("provider_url", "The OpenID Connect Provider URL").
Default(authConfig.ProviderURL).
StringVar(&authConfig.ProviderURL)
cmd.Flag("client_id", "The OpenID Connect Client ID").
Default(authConfig.ClientID).
StringVar(&authConfig.ClientID)
cmd.Flag("client_secret", "The OpenID Connect Client Secret").
StringVar(&authConfig.ClientSecret)
cmd.Flag("disable_pkce", "Disable the use of PKCE in the OIDC code flow").
BoolVar(&authConfig.DisablePKCE)
cmd.Flag("disable_nonce", "Disable the use of a nonce included and verified in the token").
BoolVar(&authConfig.DisableNonce)
cmd.Flag("agent", "The executable and arguments of the local browser to use").
StringsVar(&authConfig.AgentCommand)
}
// ConfigureAuth configures the auth command with arguments and flags
func ConfigureAuth(app *kingpin.Application, config *GlobalConfig) {
cmd := app.Command("auth", "Authenticate to the identity provider, and assume a role in AWS")
providers := append(config.AuthProvider, AuthConfig{Name: "default"})
for _, a := range providers {
authConfig := a
pcmd := cmd.Command(authConfig.Name, "Authenticate using the named profile in the config file")
configureFlags(pcmd, &authConfig)
pcmd.Action(func(c *kingpin.ParseContext) error {
if authConfig.ClientID == "" {
return fmt.Errorf("Missing ClientID for provider %s", authConfig.Name)
}
if _, err := url.ParseRequestURI(authConfig.ProviderURL); err != nil {
return fmt.Errorf("Missing ProviderURL, or invalid format for provider %s", authConfig.Name)
}
if len(authConfig.AgentCommand) == 0 {
return fmt.Errorf("Missing Agent command for provider %s", authConfig.Name)
}
if _, err := arn.Parse(authConfig.RoleArn); err != nil {
return fmt.Errorf("Missing RoleArn, or invalid format for provider %s", authConfig.Name)
}
AuthCommand(app, config, &authConfig)
return nil
})
if authConfig.Name == "default" {
pcmd.Default()
}
}
}
// AuthCommand executes the authentication with the selected OpenIDConnect provider
func AuthCommand(app *kingpin.Application, config *GlobalConfig, authConfig *AuthConfig) {
p := &provider.ProviderConfig{
ClientID: authConfig.ClientID,
ClientSecret: authConfig.ClientSecret,
ProviderURL: authConfig.ProviderURL,
PKCE: !authConfig.DisablePKCE,
Nonce: !authConfig.DisableNonce,
AgentCommand: authConfig.AgentCommand,
}
oauth2Token := provider.OAuth2Token{}
item, err := (*config.Keyring).Get(authConfig.ClientID)
if err != keyring.ErrKeyNotFound {
if err := json.Unmarshal(item.Data, &oauth2Token); err != nil {
// Log this error only, because we can attempt to recover by getting a new token
app.Errorf("Unable to unmarshal OAuth2Token from keychain: %v", err)
}
}
err = p.Authenticate(&oauth2Token)
app.FatalIfError(err, "Error authenticating with identity provider")
AWSCredentialsJSON, err := assumeRoleWithWebIdentity(authConfig, oauth2Token.IDToken)
app.FatalIfError(err, "Error assume role with web identity")
json, err := json.Marshal(&oauth2Token)
app.FatalIfError(err, "Error marshalling OAuth2 token")
err = (*config.Keyring).Set(keyring.Item{
Key: authConfig.ClientID,
Data: json,
Label: fmt.Sprintf("OAuth2 token for %s", authConfig.RoleArn),
Description: "OIDC OAuth2 Token",
})
app.FatalIfError(err, "Error storing OAuth2 Token in keychain")
fmt.Printf(AWSCredentialsJSON)
}
func assumeRoleWithWebIdentity(authConfig *AuthConfig, idToken string) (string, error) {
svc := sts.New(session.New())
input := &sts.AssumeRoleWithWebIdentityInput{
DurationSeconds: aws.Int64(authConfig.Duration),
RoleArn: aws.String(authConfig.RoleArn),
RoleSessionName: aws.String("aws-oidc"),
WebIdentityToken: aws.String(idToken),
}
assumeRoleResult, err := svc.AssumeRoleWithWebIdentity(input)
if err != nil {
return "", err
}
expiry := *assumeRoleResult.Credentials.Expiration
credentialData := AwsCredentialHelperData{
Version: 1,
AccessKeyID: *assumeRoleResult.Credentials.AccessKeyId,
SecretAccessKey: *assumeRoleResult.Credentials.SecretAccessKey,
SessionToken: *assumeRoleResult.Credentials.SessionToken,
Expiration: expiry.Format("2006-01-02T15:04:05Z"),
}
credentialJSON, err := json.Marshal(&credentialData)
if err != nil {
return "", err
}
return string(credentialJSON), nil
}
func max(x, y int64) int64 {
if x > y {
return x
}
return y
}

View File

@ -1,164 +1,140 @@
package cli
import (
"encoding/json"
"fmt"
"github.com/99designs/keyring"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/stoggi/aws-oidc/provider"
"golang.org/x/oauth2"
"log"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
"gopkg.in/alecthomas/kingpin.v2"
)
// ExecConfig stores the parameters needed for an exec command
type ExecConfig struct {
RoleArn string
Duration int64
ProviderURL string
ClientID string
ClientSecret string
PKCE bool
Nonce bool
ReAuth bool
AgentCommand []string
}
// json metadata for AWS credential process. Ref: https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes
type AwsCredentialHelperData struct {
Version int `json:"Version"`
AccessKeyID string `json:"AccessKeyId"`
SecretAccessKey string `json:"SecretAccessKey"`
SessionToken string `json:"SessionToken"`
Expiration string `json:"Expiration,omitempty"`
}
type LambdaPayload struct {
Role string `json:"role"`
Token string `json:"token"`
Profile string
Command string
Args []string
Signals chan os.Signal
}
// ConfigureExec configures the exec command with arguments and flags
func ConfigureExec(app *kingpin.Application, config *GlobalConfig) {
execConfig := ExecConfig{}
cmd := app.Command("exec", "Execute a command with temporary AWS credentials")
cmd := app.Command("exec", "Retrieve temporary credentials and set them as environment variables")
cmd.Default()
cmd.Arg("profile", "Name of the profile").
StringVar(&config.Profile)
cmd.Flag("role_arn", "The AWS role you want to assume").
Required().
StringVar(&execConfig.RoleArn)
cmd.Arg("cmd", "Command to execute").
Default(os.Getenv("SHELL")).
StringVar(&execConfig.Command)
cmd.Flag("duration", "The duration to assume the role for in seconds").
Default("3600").
Int64Var(&execConfig.Duration)
cmd.Flag("provider_url", "The OpenID Connect Provider URL").
Required().
StringVar(&execConfig.ProviderURL)
cmd.Flag("client_id", "The OpenID Connect Client ID").
Required().
StringVar(&execConfig.ClientID)
cmd.Flag("client_secret", "The OpenID Connect Client Secret").
Default("").
StringVar(&execConfig.ClientSecret)
cmd.Flag("pkce", "Use PKCE in the OIDC code flow").
Default("true").
BoolVar(&execConfig.PKCE)
cmd.Flag("nonce", "Require a nonce included and verified in the token").
Default("true").
BoolVar(&execConfig.Nonce)
cmd.Flag("reauth", "Require reauthentication by the identity provider").
Default("false").
BoolVar(&execConfig.ReAuth)
cmd.Arg("agent", "The executable and arguments of the local browser to use").
Default("open", "{}").
StringsVar(&execConfig.AgentCommand)
cmd.Arg("args", "Command arguments").
StringsVar(&execConfig.Args)
cmd.Action(func(c *kingpin.ParseContext) error {
execConfig.Signals = make(chan os.Signal)
ExecCommand(app, config, &execConfig)
return nil
})
}
// ExecCommand retrieves temporary credentials and sets them as environment variables
func ExecCommand(app *kingpin.Application, config *GlobalConfig, execConfig *ExecConfig) {
p := &provider.ProviderConfig{
ClientID: execConfig.ClientID,
ClientSecret: execConfig.ClientSecret,
ProviderURL: execConfig.ProviderURL,
PKCE: execConfig.PKCE,
Nonce: execConfig.Nonce,
ReAuth: execConfig.ReAuth,
AgentCommand: execConfig.AgentCommand,
if os.Getenv("AWS_OIDC") != "" {
app.Fatalf("aws-vault sessions should be nested with care, unset $AWS_OIDC to force")
return
}
oauth2Token := provider.OAuth2Token{}
item, err := (*config.Keyring).Get(execConfig.ClientID)
if err != keyring.ErrKeyNotFound {
if err := json.Unmarshal(item.Data, &oauth2Token); err != nil {
// Log this error only, because we can attempt to recover by getting a new token
app.Errorf("Unable to unmarshal OAuth2Token from keychain: %v", err)
val, err := config.Session.Config.Credentials.Get()
if err != nil {
app.Fatalf("Unable to get credentials for profile: %s", config.Profile)
}
env := environ(os.Environ())
env.Set("AWS_OIDC", config.Profile)
env.Unset("AWS_ACCESS_KEY_ID")
env.Unset("AWS_SECRET_ACCESS_KEY")
env.Unset("AWS_CREDENTIAL_FILE")
env.Unset("AWS_DEFAULT_PROFILE")
env.Unset("AWS_PROFILE")
if config.Region != "" {
log.Printf("Setting subprocess env: AWS_DEFAULT_REGION=%s, AWS_REGION=%s", config.Region, config.Region)
env.Set("AWS_DEFAULT_REGION", config.Region)
env.Set("AWS_REGION", config.Region)
}
log.Println("Setting subprocess env: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY")
env.Set("AWS_ACCESS_KEY_ID", val.AccessKeyID)
env.Set("AWS_SECRET_ACCESS_KEY", val.SecretAccessKey)
if val.SessionToken != "" {
log.Println("Setting subprocess env: AWS_SESSION_TOKEN, AWS_SECURITY_TOKEN")
env.Set("AWS_SESSION_TOKEN", val.SessionToken)
env.Set("AWS_SECURITY_TOKEN", val.SessionToken)
}
cmd := exec.Command(execConfig.Command, execConfig.Args...)
cmd.Env = env
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
signal.Notify(execConfig.Signals, os.Interrupt, os.Kill)
if err := cmd.Start(); err != nil {
app.Fatalf("%v", err)
}
// wait for the command to finish
waitCh := make(chan error, 1)
go func() {
waitCh <- cmd.Wait()
close(waitCh)
}()
for {
select {
case sig := <-execConfig.Signals:
if err = cmd.Process.Signal(sig); err != nil {
app.Errorf("%v", err)
break
}
case err := <-waitCh:
var waitStatus syscall.WaitStatus
if exitError, ok := err.(*exec.ExitError); ok {
waitStatus = exitError.Sys().(syscall.WaitStatus)
os.Exit(waitStatus.ExitStatus())
}
if err != nil {
app.Fatalf("%v", err)
}
return
}
}
err = p.Authenticate(&oauth2Token)
app.FatalIfError(err, "Error authenticating with identity provider")
AWSCredentialsJSON, err := assumeRoleWithWebIdentity(execConfig, oauth2Token.IDToken)
app.FatalIfError(err, "Error assume role with web identity")
json, err := json.Marshal(&oauth2Token)
app.FatalIfError(err, "Error marshalling OAuth2 token")
err = (*config.Keyring).Set(keyring.Item{
Key: execConfig.ClientID,
Data: json,
Label: fmt.Sprintf("OAuth2 token for %s", execConfig.RoleArn),
Description: "OIDC OAuth2 Token",
})
app.FatalIfError(err, "Error storing OAuth2 Token in keychain")
fmt.Printf(AWSCredentialsJSON)
}
func assumeRoleWithWebIdentity(execConfig *ExecConfig, idToken string) (string, error) {
// environ is a slice of strings representing the environment, in the form "key=value".
type environ []string
svc := sts.New(session.New())
input := &sts.AssumeRoleWithWebIdentityInput{
DurationSeconds: aws.Int64(execConfig.Duration),
RoleArn: aws.String(execConfig.RoleArn),
RoleSessionName: aws.String("aws-oidc"),
WebIdentityToken: aws.String(idToken),
// Unset an environment variable by key
func (e *environ) Unset(key string) {
for i := range *e {
if strings.HasPrefix((*e)[i], key+"=") {
(*e)[i] = (*e)[len(*e)-1]
*e = (*e)[:len(*e)-1]
break
}
}
assumeRoleResult, err := svc.AssumeRoleWithWebIdentity(input)
if err != nil {
return "", err
}
expiry := *assumeRoleResult.Credentials.Expiration
credentialData := AwsCredentialHelperData{
Version: 1,
AccessKeyID: *assumeRoleResult.Credentials.AccessKeyId,
SecretAccessKey: *assumeRoleResult.Credentials.SecretAccessKey,
SessionToken: *assumeRoleResult.Credentials.SessionToken,
Expiration: expiry.Format("2006-01-02T15:04:05Z"),
}
json, err := json.Marshal(&credentialData)
if err != nil {
return "", err
}
return string(json), nil
}
// Set adds an environment variable, replacing any existing ones of the same key
func (e *environ) Set(key, val string) {
e.Unset(key)
*e = append(*e, key+"="+val)
}

View File

@ -1,41 +1,54 @@
package cli
import (
"github.com/99designs/aws-vault/vault"
"github.com/99designs/keyring"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
// GlobalConfig used for defaults and command line arguments
type GlobalConfig struct {
//Region in AWS used by KMSAuth and BLESS
Region string
Region string
Profile string
AuthProvider []AuthConfig
AwsConfig *vault.Config
Keyring *keyring.Keyring
Session *session.Session
Keyring *keyring.Keyring
}
// ConfigureGlobal application arguments and flags
func ConfigureGlobal(app *kingpin.Application, config *GlobalConfig) {
app.Flag("region", "The region in AWS").
Default("ap-southeast-2").
Default(config.Region).
Envar("AWS_REGION").
StringVar(&config.Region)
app.Flag("profile", "The profile to use as defined in the AWS config file").
Default(config.Profile).
Envar("AWS_PROFILE").
StringVar(&config.Profile)
app.PreAction(func(c *kingpin.ParseContext) (err error) {
// Attempt to open the aws-vault keychain
keychain, err := keyring.Open(keyring.Config{
KeychainName: "aws-oidc",
ServiceName: "aws-oidc",
AllowedBackends: []keyring.BackendType{keyring.KeychainBackend},
KeychainName: "aws-oidc",
ServiceName: "aws-oidc",
AllowedBackends: []keyring.BackendType{keyring.KeychainBackend},
KeychainTrustApplication: true,
})
kingpin.FatalIfError(err, "Could not open aws-vault keychain")
//config.AwsConfig = awsConfig
config.Keyring = &keychain
config.Session = session.Must(session.NewSessionWithOptions(session.Options{
Config: aws.Config{Region: aws.String(config.Region)},
Profile: config.Profile,
SharedConfigState: session.SharedConfigEnable,
}))
return nil
})

View File

@ -5,16 +5,18 @@ import (
"fmt"
"net/url"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/iam"
jmespath "github.com/jmespath/go-jmespath"
"gopkg.in/alecthomas/kingpin.v2"
)
// ListConfig stores the parameters needed for a List command
type ListConfig struct {
ClientID string
Claim string
}
// ConfigureList configures the list command with arguments and flags
func ConfigureList(app *kingpin.Application, config *GlobalConfig) {
listConfig := ListConfig{}
@ -25,15 +27,20 @@ func ConfigureList(app *kingpin.Application, config *GlobalConfig) {
Required().
StringVar(&listConfig.ClientID)
cmd.Flag("claim", "The claim used in the IAM policies, prrovider:claim").
Required().
StringVar(&listConfig.Claim)
cmd.Action(func(c *kingpin.ParseContext) error {
ListCommand(app, config, &listConfig)
return nil
})
}
// ListCommand retrieves the list of AWS roles that have trust policues that accept a given client_id
func ListCommand(app *kingpin.Application, config *GlobalConfig, listConfig *ListConfig) {
svc := iam.New(session.New())
svc := iam.New(config.Session)
input := &iam.ListRolesInput{}
listRoleResult, err := svc.ListRoles(input)
@ -47,9 +54,13 @@ func ListCommand(app *kingpin.Application, config *GlobalConfig, listConfig *Lis
var d interface{}
err = json.Unmarshal([]byte(decodedValue), &d)
app.FatalIfError(err, "Unable to unmarshall AssumeRolePolicyDocument")
v, err := jmespath.Search("contains(Statement[].Condition.StringEquals.\"openid-connect.onelogin.com/oidc:aud\", `abc`)", d)
app.FatalIfError(err, "Unable to parse AssumeRolePolicyDocument")
fmt.Println(v)
query := fmt.Sprintf("contains(Statement[].Condition.StringEquals.\"%s\", '%s')", listConfig.Claim, listConfig.ClientID)
containsClientID, err := jmespath.Search(query, d)
app.FatalIfError(err, "Unable to parse AssumeRolePolicyDocument")
if containsClientID.(bool) {
fmt.Println(*role.RoleName)
fmt.Println(*role.Arn)
}
}
}

View File

@ -20,7 +20,12 @@ func execDir() string {
return ""
}
// GetConfigFilePath returns the path of the configuration file
func GetConfigFilePath() string {
return filepath.Join(homeDir(), ".aws-oidc/config")
}
// GetLogPath returns the path that should be used to store logs
func GetLogPath() string {
return filepath.Join(homeDir(), "./Library/Logs/aws-oidc.log")
return filepath.Join(homeDir(), "Library/Logs/aws-oidc.log")
}

1
go.mod
View File

@ -3,6 +3,7 @@ module github.com/stoggi/aws-oidc
require (
github.com/99designs/aws-vault v4.5.1+incompatible
github.com/99designs/keyring v0.0.0-20190110203331-82da6802f65f
github.com/BurntSushi/toml v0.3.1
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
github.com/aulanov/go.dbus v0.0.0-20150729231527-25c3068a42a0 // indirect

2
go.sum
View File

@ -3,6 +3,8 @@ github.com/99designs/aws-vault v4.5.1+incompatible h1:VjWncFWraO5K5HTRo34YMq2Mkp
github.com/99designs/aws-vault v4.5.1+incompatible/go.mod h1:BKt7gBiUkiAOh7TP/c36gMpRJkIk5F8hStyQoWwC/Rw=
github.com/99designs/keyring v0.0.0-20190110203331-82da6802f65f h1:WXiWWJrYCaOaYimBAXlRdRJ7qOisrYyMLYnCvvhHVms=
github.com/99designs/keyring v0.0.0-20190110203331-82da6802f65f/go.mod h1:aKt8W/yd91/xHY6ixZAJZ2vYbhr3pP8DcrvuGSGNPJk=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=

View File

@ -24,7 +24,6 @@ type ProviderConfig struct {
ProviderURL string
PKCE bool
Nonce bool
ReAuth bool
AgentCommand []string
}
@ -115,12 +114,10 @@ func (p *ProviderConfig) Authenticate(t *OAuth2Token) error {
}
if t != nil {
if err := t.Refresh(&config); err == nil {
log.Println("Refreshed token successfully")
if err := t.Refresh(&config); err != nil {
return nil
} else {
log.Println(err)
}
log.Println(err)
}
stateData := make([]byte, 32)
@ -159,10 +156,6 @@ func (p *ProviderConfig) Authenticate(t *OAuth2Token) error {
authCodeOptions = append(authCodeOptions, oauth2.SetAuthURLParam("nonce", nonce))
}
if p.ReAuth {
authCodeOptions = append(authCodeOptions, oauth2.SetAuthURLParam("acr_values", "onelogin:nist:level:1:re-auth"))
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
url := config.AuthCodeURL(state, authCodeOptions...)
http.Redirect(w, r, url, http.StatusFound)
@ -211,13 +204,18 @@ func (p *ProviderConfig) Authenticate(t *OAuth2Token) error {
// Filter the commands, and replace "{}" with our callback url
c := p.AgentCommand[:0]
replacedURL := false
for _, arg := range p.AgentCommand {
if arg == "{}" {
c = append(c, baseURL)
replacedURL = true
} else {
c = append(c, arg)
}
}
if !replacedURL {
c = append(c, baseURL)
}
cmd := exec.Command(c[0], c[1:]...)
cmd.Run()