From 40aa988042212b90ce11c0c5f27ff3f6ffc0c611 Mon Sep 17 00:00:00 2001 From: Ido Perlmuter Date: Mon, 30 May 2022 14:13:05 +0300 Subject: [PATCH] Allow configuring a proxy URL The ability to configure a proxy URL endpoint for the Okta API is added to the provider. This is useful for unit testing or running local caching proxies. A new configuration option called "http_proxy" is added, which by default takes the value of the "OKTA_HTTP_PROXY" environment variable. When used, and if the provided endpoint is an HTTP endpoint (rather than an HTTPS endpoint), the HTTPS check is automatically disabled. A unit test called `TestHTTPProxy` is added which also showcases unit testing of the Okta API without actually contacting Okta. Information about the new configuration option is also added to the documentation. --- okta/config.go | 16 +++++++++- okta/provider.go | 11 +++++++ okta/provider_test.go | 53 ++++++++++++++++++++++++++++++++ website/docs/index.html.markdown | 20 ++++++------ 4 files changed, 90 insertions(+), 10 deletions(-) diff --git a/okta/config.go b/okta/config.go index a5760249f..5e1f38308 100644 --- a/okta/config.go +++ b/okta/config.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "os" + "strings" "time" "github.com/hashicorp/go-cleanhttp" @@ -33,6 +34,7 @@ type ( Config struct { orgName string domain string + httpProxy string apiToken string clientID string privateKey string @@ -90,8 +92,17 @@ func (c *Config) loadAndValidate(ctx context.Context) error { httpClient.Transport = transport.NewGovernedTransport(httpClient.Transport, apiMutex, c.logger) } + var orgUrl string + var disableHTTPS bool + if c.httpProxy != "" { + orgUrl = strings.TrimSuffix(c.httpProxy, "/") + disableHTTPS = strings.HasPrefix(orgUrl, "http://") + } else { + orgUrl = fmt.Sprintf("https://%v.%v", c.orgName, c.domain) + } + setters := []okta.ConfigSetter{ - okta.WithOrgUrl(fmt.Sprintf("https://%v.%v", c.orgName, c.domain)), + okta.WithOrgUrl(orgUrl), okta.WithToken(c.apiToken), okta.WithClientId(c.clientID), okta.WithPrivateKey(c.privateKey), @@ -106,6 +117,9 @@ func (c *Config) loadAndValidate(ctx context.Context) error { if c.apiToken == "" { setters = append(setters, okta.WithAuthorizationMode("PrivateKey")) } + if disableHTTPS { + setters = append(setters, okta.WithTestingDisableHttpsCheck(true)) + } _, client, err := okta.NewClient( context.Background(), setters..., diff --git a/okta/provider.go b/okta/provider.go index 372c8160b..7e7cd87ba 100644 --- a/okta/provider.go +++ b/okta/provider.go @@ -179,6 +179,12 @@ func Provider() *schema.Provider { DefaultFunc: schema.EnvDefaultFunc("OKTA_BASE_URL", "okta.com"), Description: "The Okta url. (Use 'oktapreview.com' for Okta testing)", }, + "http_proxy": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("OKTA_HTTP_PROXY", ""), + Description: "Alternate HTTP proxy of scheme://hostname or scheme://hostname:port format", + }, "backoff": { Type: schema.TypeBool, Optional: true, @@ -420,6 +426,11 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{} requestTimeout: d.Get("request_timeout").(int), maxAPICapacity: d.Get("max_api_capacity").(int), } + + if httpProxy, ok := d.Get("http_proxy").(string); ok { + config.httpProxy = httpProxy + } + if v := os.Getenv("OKTA_API_SCOPES"); v != "" && len(config.scopes) == 0 { config.scopes = strings.Split(v, ",") } diff --git a/okta/provider_test.go b/okta/provider_test.go index 3f1385fe5..6a5fe2b61 100644 --- a/okta/provider_test.go +++ b/okta/provider_test.go @@ -2,13 +2,18 @@ package okta import ( "context" + "encoding/json" "errors" "fmt" + "net/http" + "net/http/httptest" "os" "strings" "testing" + "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/okta/okta-sdk-golang/v2/okta" ) var ( @@ -39,6 +44,7 @@ func oktaConfig() (*Config, error) { config := &Config{ orgName: os.Getenv("OKTA_ORG_NAME"), apiToken: os.Getenv("OKTA_API_TOKEN"), + httpProxy: os.Getenv("OKTA_HTTP_PROXY"), clientID: os.Getenv("OKTA_API_CLIENT_ID"), privateKey: os.Getenv("OKTA_API_PRIVATE_KEY"), scopes: strings.Split(os.Getenv("OKTA_API_SCOPES"), ","), @@ -75,3 +81,50 @@ func accPreCheck() error { } return nil } + +func TestHTTPProxy(t *testing.T) { + var handledUserRequest bool + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + now := time.Now() + w.Header().Set("Content-Type", "application/json") + w.Header().Set("x-rate-limit-reset", "0") + w.Header().Set("x-rate-limit-limit", "0") + w.Header().Set("x-rate-limit-limit", "0") + w.Header().Set("x-rate-limit-remaining", "0") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(&okta.User{ + Id: "fake-user", + LastLogin: &now, + LastUpdated: &now, + }) + handledUserRequest = true + })) + + defer ts.Close() + + os.Setenv("OKTA_HTTP_PROXY", ts.URL) + os.Setenv("OKTA_ORG_NAME", "unit-testing") + os.Setenv("OKTA_API_TOKEN", "fake-token") + t.Cleanup(func() { + os.Unsetenv("OKTA_HTTP_PROXY") + os.Unsetenv("OKTA_ORG_NAME") + os.Unsetenv("OKTA_API_TOKEN") + }) + + err := accPreCheck() + if err != nil { + t.Fatalf("Did not expect accPreCheck() to fail: %s", err) + } + + c, err := oktaConfig() + if err != nil { + t.Fatalf("Did not expect oktaConfig() to fail: %s", err) + } + if c.httpProxy != ts.URL { + t.Fatalf("Execpted httpProxy to be %q, got %q", ts.URL, c.httpProxy) + } + if !handledUserRequest { + t.Fatal("Expected local server to handle user request, but it didn't") + } +} diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index 87ec33c54..2f4765260 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -1,7 +1,7 @@ --- -layout: 'okta' -page_title: 'Provider: Okta' -sidebar_current: 'docs-okta-index' +layout: "okta" +page_title: "Provider: Okta" +sidebar_current: "docs-okta-index" description: |- The Okta provider is used to interact with the resources supported by Okta. The provider needs to be configured with the proper credentials before it can be used. --- @@ -50,9 +50,9 @@ explained below: ### Environment variables -You can provide your credentials via the `OKTA_ORG_NAME`, `OKTA_BASE_URL`, `OKTA_API_TOKEN`, `OKTA_API_CLIENT_ID`, -`OKTA_API_SCOPES` and `OKTA_API_PRIVATE_KEY` environment variables, representing your Okta Organization Name, -Okta Base URL (i.e. `"okta.com"` or `"oktapreview.com"`), Okta API Token, Okta Client ID, Okta API scopes +You can provide your credentials via the `OKTA_ORG_NAME`, `OKTA_BASE_URL`, `OKTA_API_TOKEN`, `OKTA_API_CLIENT_ID`, +`OKTA_API_SCOPES` and `OKTA_API_PRIVATE_KEY` environment variables, representing your Okta Organization Name, +Okta Base URL (i.e. `"okta.com"` or `"oktapreview.com"`), Okta API Token, Okta Client ID, Okta API scopes and Okta API private key respectively. ```hcl @@ -79,7 +79,9 @@ In addition to [generic `provider` arguments](https://www.terraform.io/docs/conf - `base_url` - (Optional) This is the domain of your Okta account, for example `dev-123456.oktapreview.com` would have a base url of `oktapreview.com`. It must be provided, but it can also be sourced from the `OKTA_BASE_URL` environment variable. -- `api_token` - (Optional) This is the API token to interact with your Okta org. It can also be sourced from the `OKTA_API_TOKEN` environment variable. `api_token` conflicts with `client_id`, `scopes` and `private_key`. +- `http_proxy` - (Optional) This is a custom URL endpoint that can be used for unit testing or local caching proxies. Can also be sourced from the `OKTA_HTTP_PROXY` environment variable. + +- `api_token` - (Optional) This is the API token to interact with your Okta org. It can also be sourced from the `OKTA_API_TOKEN` environment variable. `api_token` conflicts with `client_id`, `scopes` and `private_key`. - `client_id` - (Optional) This is the client ID for obtaining the API token. It can also be sourced from the `OKTA_API_CLIENT_ID` environment variable. `client_id` conflicts with `api_token`. @@ -97,6 +99,6 @@ In addition to [generic `provider` arguments](https://www.terraform.io/docs/conf - `request_timeout` - (Optional) Timeout for single request (in seconds) which is made to Okta, the default is `0` (means no limit is set). The maximum value can be `300`. -- `max_api_capacity` - (Optional, experimental) sets what percentage of capacity the provider can use of the total - rate limit capacity while making calls to the Okta management API endpoints. Okta API operates in one minute buckets. +- `max_api_capacity` - (Optional, experimental) sets what percentage of capacity the provider can use of the total + rate limit capacity while making calls to the Okta management API endpoints. Okta API operates in one minute buckets. See Okta Management API Rate Limits: https://developer.okta.com/docs/reference/rl-global-mgmt. Can be set to a value between 1 and 100.