diff --git a/README.md b/README.md index cafa22247..00edc8d59 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ The process goes something like this: * [F5APM](pkg/provider/f5apm/README.md) * [Akamai](pkg/provider/akamai/README.md) * OneLogin + * NetIQ * AWS SAML Provider configured ## Caveats @@ -486,7 +487,28 @@ aws iam list-groups } } ``` +## Advanced Configuration - additional parameters +There are few additional parameters allowing to customise saml2aws configuration. +Use following parameters in `~/.saml2aws` file: +- `http_attempts_count` - configures the number of attempts to send http requests in order to authorise with saml provider. Defaults to 1 +- `http_retry_delay` - configures the duration (in seconds) of timeout between attempts to send http requests to saml provider. Defaults to 1 +Example: typical configuration with such parameters would look like follows: +``` +[default] +url = https://id.customer.cloud +username = user@versent.com.au +provider = Ping +mfa = Auto +skip_verify = false +timeout = 0 +aws_urn = urn:amazon:webservices +aws_session_duration = 28800 +aws_profile = customer-dev +role_arn = arn:aws:iam::121234567890:role/customer-admin-role +http_attempts_count = 3 +http_retry_delay = 1 +``` ## Building To build this software on osx clone to the repo to `$GOPATH/src/github.com/versent/saml2aws` and ensure you have `$GOPATH/bin` in your `$PATH`. diff --git a/go.mod b/go.mod index a7a45d54c..a802db83f 100644 --- a/go.mod +++ b/go.mod @@ -12,11 +12,10 @@ require ( github.com/alecthomas/units v0.0.0-20190910110746-680d30ca3117 // indirect github.com/andybalholm/cascadia v1.0.0 // indirect github.com/aulanov/go.dbus v0.0.0-20150729231527-25c3068a42a0 // indirect + github.com/avast/retry-go v2.6.0+incompatible github.com/aws/aws-sdk-go v1.23.15 - github.com/axw/gocov v1.0.0 // indirect github.com/beevik/etree v1.0.1 github.com/briandowns/spinner v0.0.0-20170614154858-48dbb65d7bd5 - github.com/buildkite/github-release v0.0.0-20200205104344-c0a6db85594d // indirect github.com/danieljoos/wincred v1.0.1 github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae // indirect github.com/fatih/color v1.7.0 // indirect @@ -30,7 +29,6 @@ require ( github.com/marshallbrekka/go-u2fhost v0.0.0-20200107013215-ad5fdc1986ac github.com/mattn/go-isatty v0.0.8 github.com/mitchellh/go-homedir v1.0.0 - github.com/mitchellh/gox v1.0.1 // indirect github.com/onsi/ginkgo v1.10.1 // indirect github.com/onsi/gomega v1.7.0 // indirect github.com/pkg/errors v0.8.1 diff --git a/go.sum b/go.sum index 06423e81c..3c1d4168c 100644 --- a/go.sum +++ b/go.sum @@ -18,16 +18,14 @@ github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRy github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/aulanov/go.dbus v0.0.0-20150729231527-25c3068a42a0 h1:EEDvbomAQ+MFWqJ9FM6RXyJTkc4lckyWsbc5CGQkG1Y= github.com/aulanov/go.dbus v0.0.0-20150729231527-25c3068a42a0/go.mod h1:VHvUx+4lTCaJ8zUnEXF4cWEc9c8lnDt4PGLwlZ+3yaM= +github.com/avast/retry-go v2.6.0+incompatible h1:FelcMrm7Bxacr1/RM8+/eqkDkmVN7tjlsy51dOzB3LI= +github.com/avast/retry-go v2.6.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.23.15 h1:ut2ZzO0A34Ds18NXvvkWWKyO4aZqQ9uZquslWzCQvGU= github.com/aws/aws-sdk-go v1.23.15/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/axw/gocov v1.0.0 h1:YsqYR66hUmilVr23tu8USgnJIJvnwh3n7j5zRn7x4LU= -github.com/axw/gocov v1.0.0/go.mod h1:LvQpEYiwwIb2nYkXY2fDWhg9/AsYqkhmrCshjlUJECE= github.com/beevik/etree v1.0.1 h1:lWzdj5v/Pj1X360EV7bUudox5SRipy4qZLjY0rhb0ck= github.com/beevik/etree v1.0.1/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/briandowns/spinner v0.0.0-20170614154858-48dbb65d7bd5 h1:osZyZB7J4kE1tKLeaUjV6+uZVBfS835T0I/RxmwWw1w= github.com/briandowns/spinner v0.0.0-20170614154858-48dbb65d7bd5/go.mod h1:hw/JEQBIE+c/BLI4aKM8UU8v+ZqrD3h7HC27kKt8JQU= -github.com/buildkite/github-release v0.0.0-20200205104344-c0a6db85594d h1:dSMX9iqIZScLmKhyJ+xQcn+gTU2RS5sKpNpRELYod0g= -github.com/buildkite/github-release v0.0.0-20200205104344-c0a6db85594d/go.mod h1:Hd/sX79FgO4Y8/8nczoszTudx5egMjvbUxQgUZ6W2jc= github.com/danieljoos/wincred v1.0.1 h1:fcRTaj17zzROVqni2FiToKUVg3MmJ4NtMSGCySPIr/g= github.com/danieljoos/wincred v1.0.1/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= @@ -46,18 +44,12 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/go-github v8.0.1-0.20170604030111-7a51fb928f52+incompatible h1:XfCpf6Ak5XPaZpv2rlCqfBuKoFQH5SvXvpQIiV1H7H4= -github.com/google/go-github v8.0.1-0.20170604030111-7a51fb928f52+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= -github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= -github.com/hashicorp/go-version v1.0.0 h1:21MVWPKDphxa7ineQQTrCU5brh7OuVVAzGOCnnCPtE8= -github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -89,12 +81,6 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1f github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI= -github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4= -github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/oleiade/reflections v0.0.0-20160817071559-0e86b3c98b2f h1:I6mXuorHlvwNDFelz7a+j0HaGYSzX7+Gq60DqLVypfc= -github.com/oleiade/reflections v0.0.0-20160817071559-0e86b3c98b2f/go.mod h1:RbATFBbKYkVdqmSFtx13Bb/tVhR0lgOBXunWTZKeL4w= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -128,7 +114,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/net v0.0.0-20170218080159-6b27048ae5e6/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= @@ -136,11 +121,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190916140828-c8589233b77d h1:mCMDWKhNO37A7GAhOpHPbIw1cjd0V86kX1/WA9c7FZ8= golang.org/x/net v0.0.0-20190916140828-c8589233b77d/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20170517174439-f047394b6d14 h1:5fvqvN1Glau4dT9f9CReJ/5DQdgl50EfDOzX7jBecZg= -golang.org/x/oauth2 v0.0.0-20170517174439-f047394b6d14/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -155,8 +137,6 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190617190820-da514acc4774 h1:CQVOmarCBFzTx0kbOU0ru54Cvot8SdSrNYjZPhQl+gk= -golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/cfg/cfg.go b/pkg/cfg/cfg.go index 00f877b15..bf8fd5b22 100644 --- a/pkg/cfg/cfg.go +++ b/pkg/cfg/cfg.go @@ -43,6 +43,8 @@ type IDPAccount struct { ResourceID string `ini:"resource_id"` // used by F5APM Subdomain string `ini:"subdomain"` // used by OneLogin RoleARN string `ini:"role_arn"` + HttpAttemptsCount string `ini:"http_attempts_count"` + HttpRetryDelay string `ini:"http_retry_delay"` } func (ia IDPAccount) String() string { diff --git a/pkg/provider/aad/aad.go b/pkg/provider/aad/aad.go index 9e6430060..4e9b7b0bd 100644 --- a/pkg/provider/aad/aad.go +++ b/pkg/provider/aad/aad.go @@ -612,7 +612,7 @@ func New(idpAccount *cfg.IDPAccount) (*Client, error) { TLSClientConfig: &tls.Config{InsecureSkipVerify: idpAccount.SkipVerify, Renegotiation: tls.RenegotiateFreelyAsClient}, } - client, err := provider.NewHTTPClient(tr) + client, err := provider.NewHTTPClient(tr, provider.BuildHttpClientOpts(idpAccount)) if err != nil { return nil, errors.Wrap(err, "error building http client") } diff --git a/pkg/provider/adfs/adfs.go b/pkg/provider/adfs/adfs.go index 54c28090e..9fbf65445 100644 --- a/pkg/provider/adfs/adfs.go +++ b/pkg/provider/adfs/adfs.go @@ -41,7 +41,7 @@ func New(idpAccount *cfg.IDPAccount) (*Client, error) { TLSClientConfig: &tls.Config{InsecureSkipVerify: idpAccount.SkipVerify, Renegotiation: tls.RenegotiateFreelyAsClient}, } - client, err := provider.NewHTTPClient(tr) + client, err := provider.NewHTTPClient(tr, provider.BuildHttpClientOpts(idpAccount)) if err != nil { return nil, errors.Wrap(err, "error building http client") } diff --git a/pkg/provider/akamai/akamai.go b/pkg/provider/akamai/akamai.go index 0b63df0b2..dd7b981a4 100644 --- a/pkg/provider/akamai/akamai.go +++ b/pkg/provider/akamai/akamai.go @@ -81,7 +81,7 @@ func New(idpAccount *cfg.IDPAccount) (*Client, error) { tr := provider.NewDefaultTransport(idpAccount.SkipVerify) - client, err := provider.NewHTTPClient(tr) + client, err := provider.NewHTTPClient(tr, provider.BuildHttpClientOpts(idpAccount)) if err != nil { return nil, errors.Wrap(err, "error building http client") } diff --git a/pkg/provider/f5apm/f5apm.go b/pkg/provider/f5apm/f5apm.go index e72cfbf1a..dc4c0d4cf 100644 --- a/pkg/provider/f5apm/f5apm.go +++ b/pkg/provider/f5apm/f5apm.go @@ -34,7 +34,7 @@ type Client struct { func New(idpAccount *cfg.IDPAccount) (*Client, error) { tr := provider.NewDefaultTransport(idpAccount.SkipVerify) - client, err := provider.NewHTTPClient(tr) + client, err := provider.NewHTTPClient(tr, provider.BuildHttpClientOpts(idpAccount)) if err != nil { return nil, errors.Wrap(err, "Error building HTTP client") } diff --git a/pkg/provider/f5apm/f5apm_test.go b/pkg/provider/f5apm/f5apm_test.go index db9442434..31485f097 100644 --- a/pkg/provider/f5apm/f5apm_test.go +++ b/pkg/provider/f5apm/f5apm_test.go @@ -28,7 +28,8 @@ func TestClient_getLoginForm(t *testing.T) { jar, err := cookiejar.New(nil) require.Nil(t, err) - ac := Client{client: &provider.HTTPClient{Client: http.Client{Jar: jar}}} + opts := &provider.HTTPClientOptions{IsWithRetries: false} + ac := Client{client: &provider.HTTPClient{Client: http.Client{Jar: jar}, Options: opts}} t.Log(ac) loginDetails := &creds.LoginDetails{URL: ts.URL, Username: "groundcontrol", Password: "majortom"} t.Log(loginDetails) @@ -52,7 +53,8 @@ func TestClient_postLoginForm_user_pass(t *testing.T) { jar, err := cookiejar.New(nil) require.Nil(t, err) - ac := Client{client: &provider.HTTPClient{Client: http.Client{Jar: jar}}} + opts := &provider.HTTPClientOptions{IsWithRetries: false} + ac := Client{client: &provider.HTTPClient{Client: http.Client{Jar: jar}, Options: opts}} t.Log(ac) loginDetails := &creds.LoginDetails{URL: ts.URL, Username: "groundcontrol", Password: "majortom"} t.Log(loginDetails) diff --git a/pkg/provider/googleapps/googleapps.go b/pkg/provider/googleapps/googleapps.go index 4021d74e4..84a19f758 100644 --- a/pkg/provider/googleapps/googleapps.go +++ b/pkg/provider/googleapps/googleapps.go @@ -30,7 +30,7 @@ func New(idpAccount *cfg.IDPAccount) (*Client, error) { tr := provider.NewDefaultTransport(idpAccount.SkipVerify) - client, err := provider.NewHTTPClient(tr) + client, err := provider.NewHTTPClient(tr, provider.BuildHttpClientOpts(idpAccount)) if err != nil { return nil, errors.Wrap(err, "error building http client") } diff --git a/pkg/provider/googleapps/googleapps_test.go b/pkg/provider/googleapps/googleapps_test.go index 9cf769033..5e7308151 100644 --- a/pkg/provider/googleapps/googleapps_test.go +++ b/pkg/provider/googleapps/googleapps_test.go @@ -33,8 +33,8 @@ func TestExtractInputsByFormQuery(t *testing.T) { doc.Url = &url.URL{ Scheme: "https", - Host: "google.com", - Path: "foobar", + Host: "google.com", + Path: "foobar", } form, actionURL, err := extractInputsByFormQuery(doc, "#dev") @@ -87,7 +87,8 @@ func TestChallengePage(t *testing.T) { })) defer ts.Close() - kc := Client{client: &provider.HTTPClient{Client: http.Client{}}} + opts := &provider.HTTPClientOptions{IsWithRetries: false} + kc := Client{client: &provider.HTTPClient{Client: http.Client{}, Options: opts}} loginDetails := &creds.LoginDetails{URL: ts.URL, Username: "test", Password: "test123"} authForm := url.Values{} diff --git a/pkg/provider/http.go b/pkg/provider/http.go index 8c4a125bb..71a2ca5d5 100644 --- a/pkg/provider/http.go +++ b/pkg/provider/http.go @@ -3,10 +3,13 @@ package provider import ( "crypto/tls" "fmt" + "github.com/avast/retry-go" + "github.com/versent/saml2aws/pkg/cfg" "net" "net/http" "os" "runtime" + "strconv" "time" "github.com/sirupsen/logrus" @@ -23,6 +26,18 @@ import ( type HTTPClient struct { http.Client CheckResponseStatus func(*http.Request, *http.Response) error + Options *HTTPClientOptions +} + +const ( + DefaultAttemptsCount = 1 + DefaultRetryDelay = time.Duration(1) * time.Second +) + +type HTTPClientOptions struct { + IsWithRetries bool //http retry feature switch + AttemptsCount uint + RetryDelay time.Duration } // NewDefaultTransport configure a transport with the TLS skip verify option @@ -42,8 +57,27 @@ func NewDefaultTransport(skipVerify bool) *http.Transport { } } +func BuildHttpClientOpts(account *cfg.IDPAccount) *HTTPClientOptions { + opts := &HTTPClientOptions{} + atmt, atmtErr := strconv.ParseUint(account.HttpAttemptsCount, 10, 0) + if opts.IsWithRetries = atmtErr == nil; opts.IsWithRetries { + opts.AttemptsCount = uint(atmt) + } else { + opts.AttemptsCount = DefaultAttemptsCount + } + + delay, delayErr := strconv.ParseUint(account.HttpRetryDelay, 10, 0) + if delayErr != nil { + opts.RetryDelay = DefaultRetryDelay + } else { + opts.RetryDelay = time.Duration(delay) * time.Second + } + + return opts +} + // NewHTTPClient configure the default http client used by the providers -func NewHTTPClient(tr http.RoundTripper) (*HTTPClient, error) { +func NewHTTPClient(tr http.RoundTripper, opts *HTTPClientOptions) (*HTTPClient, error) { options := &cookiejar.Options{ PublicSuffixList: publicsuffix.List, @@ -56,7 +90,7 @@ func NewHTTPClient(tr http.RoundTripper) (*HTTPClient, error) { client := http.Client{Transport: tr, Jar: jar} - return &HTTPClient{client, nil}, nil + return &HTTPClient{client, nil, opts}, nil } // Do do the request @@ -81,9 +115,15 @@ func (hc *HTTPClient) Do(req *http.Request) (*http.Response, error) { req.Header.Set("User-Agent", fmt.Sprintf("saml2aws/1.0 (%s %s) Versent", runtime.GOOS, runtime.GOARCH)) - hc.logHTTPRequest(req) + var resp *http.Response + var err error - resp, err := hc.Client.Do(req) + if hc.Options.IsWithRetries { + resp, err = hc.doWithRetry(req) + } else { + hc.logHTTPRequest(req) + resp, err = hc.Client.Do(req) + } if err != nil { return resp, err } @@ -101,6 +141,32 @@ func (hc *HTTPClient) Do(req *http.Request) (*http.Response, error) { return resp, err } +func (hc *HTTPClient) doWithRetry(req *http.Request) (*http.Response, error) { + var resp *http.Response + err := retry.Do( + func() error { + hc.logHTTPRequest(req) + clientResp, err := hc.Client.Do(req) + if err != nil { + return err + } + resp = clientResp + return nil + }, + retry.Attempts(hc.Options.AttemptsCount), + retry.Delay(hc.Options.RetryDelay), + retry.OnRetry( + func(n uint, err error) { + logrus. + WithField("Attempt #", n). + WithField("Caused by", fmt.Errorf("%v", err)). + Debug("Retry") + }), + ) + return resp, err + +} + // DisableFollowRedirect disable redirects func (hc *HTTPClient) DisableFollowRedirect() { hc.CheckRedirect = func(req *http.Request, via []*http.Request) error { diff --git a/pkg/provider/http_test.go b/pkg/provider/http_test.go index 0a055017d..2c7479771 100644 --- a/pkg/provider/http_test.go +++ b/pkg/provider/http_test.go @@ -15,8 +15,8 @@ func TestClientDoGetOK(t *testing.T) { defer ts.Close() rt := NewDefaultTransport(false) - - hc, err := NewHTTPClient(rt) + opts := &HTTPClientOptions{IsWithRetries: false} + hc, err := NewHTTPClient(rt, opts) require.Nil(t, err) // hc := &HTTPClient{Client: http.Client{}} @@ -39,7 +39,8 @@ func TestClientDisableRedirect(t *testing.T) { rt := NewDefaultTransport(false) - hc, err := NewHTTPClient(rt) + opts := &HTTPClientOptions{IsWithRetries: false} + hc, err := NewHTTPClient(rt, opts) require.Nil(t, err) hc.DisableFollowRedirect() @@ -59,7 +60,8 @@ func TestClientDoResponseCheck(t *testing.T) { })) defer ts.Close() - hc := &HTTPClient{Client: http.Client{}} + opts := &HTTPClientOptions{IsWithRetries: false} + hc := &HTTPClient{Client: http.Client{}, Options: opts} hc.CheckResponseStatus = SuccessOrRedirectResponseValidator diff --git a/pkg/provider/jumpcloud/jumpcloud.go b/pkg/provider/jumpcloud/jumpcloud.go index a0407817d..f44f97236 100644 --- a/pkg/provider/jumpcloud/jumpcloud.go +++ b/pkg/provider/jumpcloud/jumpcloud.go @@ -52,7 +52,7 @@ func New(idpAccount *cfg.IDPAccount) (*Client, error) { tr := provider.NewDefaultTransport(idpAccount.SkipVerify) - client, err := provider.NewHTTPClient(tr) + client, err := provider.NewHTTPClient(tr, provider.BuildHttpClientOpts(idpAccount)) if err != nil { return nil, errors.Wrap(err, "error building http client") } diff --git a/pkg/provider/keycloak/keycloak.go b/pkg/provider/keycloak/keycloak.go index cd4f0caad..ebc398fbb 100644 --- a/pkg/provider/keycloak/keycloak.go +++ b/pkg/provider/keycloak/keycloak.go @@ -27,7 +27,7 @@ func New(idpAccount *cfg.IDPAccount) (*Client, error) { tr := provider.NewDefaultTransport(idpAccount.SkipVerify) - client, err := provider.NewHTTPClient(tr) + client, err := provider.NewHTTPClient(tr, provider.BuildHttpClientOpts(idpAccount)) if err != nil { return nil, errors.Wrap(err, "error building http client") } diff --git a/pkg/provider/keycloak/keycloak_test.go b/pkg/provider/keycloak/keycloak_test.go index 9d0688891..9fb06750c 100644 --- a/pkg/provider/keycloak/keycloak_test.go +++ b/pkg/provider/keycloak/keycloak_test.go @@ -31,7 +31,8 @@ func TestClient_getLoginForm(t *testing.T) { })) defer ts.Close() - kc := Client{client: &provider.HTTPClient{Client: http.Client{}}} + opts := &provider.HTTPClientOptions{IsWithRetries: false} + kc := Client{client: &provider.HTTPClient{Client: http.Client{}, Options: opts}} loginDetails := &creds.LoginDetails{URL: ts.URL, Username: "test", Password: "test123"} submitURL, authForm, err := kc.getLoginForm(loginDetails) @@ -60,7 +61,8 @@ func TestClient_postLoginForm(t *testing.T) { "login": []string{"Log in"}, } - kc := Client{client: &provider.HTTPClient{Client: http.Client{}}} + opts := &provider.HTTPClientOptions{IsWithRetries: false} + kc := Client{client: &provider.HTTPClient{Client: http.Client{}, Options: opts}} content, err := kc.postLoginForm(ts.URL, loginForm) require.Nil(t, err) @@ -86,7 +88,8 @@ func TestClient_postTotpForm(t *testing.T) { pr.Mock.On("RequestSecurityCode", "000000").Return("123456") mfaToken := "" - kc := Client{client: &provider.HTTPClient{Client: http.Client{}}} + opts := &provider.HTTPClientOptions{IsWithRetries: false} + kc := Client{client: &provider.HTTPClient{Client: http.Client{}, Options: opts}} kc.postTotpForm(ts.URL, mfaToken, doc) @@ -110,7 +113,8 @@ func TestClient_postTotpFormWithProvidedMFAToken(t *testing.T) { prompter.SetPrompter(pr) mfaToken := "123456" - kc := Client{client: &provider.HTTPClient{Client: http.Client{}}} + opts := &provider.HTTPClientOptions{IsWithRetries: false} + kc := Client{client: &provider.HTTPClient{Client: http.Client{}, Options: opts}} kc.postTotpForm(ts.URL, mfaToken, doc) diff --git a/pkg/provider/netiq/netiq.go b/pkg/provider/netiq/netiq.go index 86e41f65f..f82792ce2 100644 --- a/pkg/provider/netiq/netiq.go +++ b/pkg/provider/netiq/netiq.go @@ -25,7 +25,7 @@ type Client struct { // New creates a new external client func New(idpAccount *cfg.IDPAccount) (*Client, error) { tr := provider.NewDefaultTransport(idpAccount.SkipVerify) - client, err := provider.NewHTTPClient(tr) + client, err := provider.NewHTTPClient(tr, provider.BuildHttpClientOpts(idpAccount)) if err != nil { return nil, errors.Wrap(err, "Error building HTTP client") } diff --git a/pkg/provider/okta/okta.go b/pkg/provider/okta/okta.go index 67832261f..a83d035bd 100644 --- a/pkg/provider/okta/okta.go +++ b/pkg/provider/okta/okta.go @@ -77,7 +77,7 @@ func New(idpAccount *cfg.IDPAccount) (*Client, error) { tr := provider.NewDefaultTransport(idpAccount.SkipVerify) - client, err := provider.NewHTTPClient(tr) + client, err := provider.NewHTTPClient(tr, provider.BuildHttpClientOpts(idpAccount)) if err != nil { return nil, errors.Wrap(err, "error building http client") } diff --git a/pkg/provider/onelogin/onelogin.go b/pkg/provider/onelogin/onelogin.go index 1eafa5272..5e9a433d6 100644 --- a/pkg/provider/onelogin/onelogin.go +++ b/pkg/provider/onelogin/onelogin.go @@ -77,7 +77,7 @@ type VerifyRequest struct { // New creates a new OneLogin client. func New(idpAccount *cfg.IDPAccount) (*Client, error) { tr := provider.NewDefaultTransport(idpAccount.SkipVerify) - client, err := provider.NewHTTPClient(tr) + client, err := provider.NewHTTPClient(tr, provider.BuildHttpClientOpts(idpAccount)) if err != nil { return nil, errors.Wrap(err, "error building http client") } diff --git a/pkg/provider/pingfed/pingfed.go b/pkg/provider/pingfed/pingfed.go index 9934509d3..6889658ef 100644 --- a/pkg/provider/pingfed/pingfed.go +++ b/pkg/provider/pingfed/pingfed.go @@ -33,7 +33,7 @@ func New(idpAccount *cfg.IDPAccount) (*Client, error) { tr := provider.NewDefaultTransport(idpAccount.SkipVerify) - client, err := provider.NewHTTPClient(tr) + client, err := provider.NewHTTPClient(tr, provider.BuildHttpClientOpts(idpAccount)) if err != nil { return nil, errors.Wrap(err, "error building http client") } diff --git a/pkg/provider/pingone/pingone.go b/pkg/provider/pingone/pingone.go index 479ed5221..404820b62 100644 --- a/pkg/provider/pingone/pingone.go +++ b/pkg/provider/pingone/pingone.go @@ -33,7 +33,7 @@ func New(idpAccount *cfg.IDPAccount) (*Client, error) { tr := provider.NewDefaultTransport(idpAccount.SkipVerify) - client, err := provider.NewHTTPClient(tr) + client, err := provider.NewHTTPClient(tr, provider.BuildHttpClientOpts(idpAccount)) if err != nil { return nil, errors.Wrap(err, "error building http client") } diff --git a/pkg/provider/shibboleth/shibboleth.go b/pkg/provider/shibboleth/shibboleth.go index a282d21ad..38bdfa1e2 100644 --- a/pkg/provider/shibboleth/shibboleth.go +++ b/pkg/provider/shibboleth/shibboleth.go @@ -34,7 +34,7 @@ func New(idpAccount *cfg.IDPAccount) (*Client, error) { TLSClientConfig: &tls.Config{InsecureSkipVerify: idpAccount.SkipVerify, Renegotiation: tls.RenegotiateFreelyAsClient}, } - client, err := provider.NewHTTPClient(tr) + client, err := provider.NewHTTPClient(tr, provider.BuildHttpClientOpts(idpAccount)) if err != nil { return nil, errors.Wrap(err, "error building http client") } diff --git a/pkg/provider/shibbolethecp/shibbolethecp.go b/pkg/provider/shibbolethecp/shibbolethecp.go index 7b9664129..ac3bb0e46 100644 --- a/pkg/provider/shibbolethecp/shibbolethecp.go +++ b/pkg/provider/shibbolethecp/shibbolethecp.go @@ -69,7 +69,7 @@ func New(idpAccount *cfg.IDPAccount) (*Client, error) { TLSClientConfig: &tls.Config{InsecureSkipVerify: idpAccount.SkipVerify, Renegotiation: tls.RenegotiateFreelyAsClient}, } - client, err := provider.NewHTTPClient(tr) + client, err := provider.NewHTTPClient(tr, provider.BuildHttpClientOpts(idpAccount)) if err != nil { return nil, errors.Wrap(err, "error building http client") }