diff --git a/nsxt/provider.go b/nsxt/provider.go index 57057e068..f9b7fa9d9 100644 --- a/nsxt/provider.go +++ b/nsxt/provider.go @@ -158,13 +158,30 @@ func Provider() *schema.Provider { Type: schema.TypeString, Optional: true, Description: "URL for VMC authorization service (CSP)", - DefaultFunc: schema.EnvDefaultFunc("NSXT_VMC_AUTH_HOST", "console.cloud.vmware.com/csp/gateway/am/api/auth/api-tokens/authorize"), + DefaultFunc: schema.EnvDefaultFunc("NSXT_VMC_AUTH_HOST", nil), }, "vmc_token": { - Type: schema.TypeString, - Optional: true, - Description: "Long-living API token for VMC authorization", - DefaultFunc: schema.EnvDefaultFunc("NSXT_VMC_TOKEN", nil), + Type: schema.TypeString, + Optional: true, + Description: "Long-living API token for VMC authorization", + DefaultFunc: schema.EnvDefaultFunc("NSXT_VMC_TOKEN", nil), + ConflictsWith: []string{"vmc_client_id", "vmc_client_secret"}, + }, + "vmc_client_id": { + Type: schema.TypeString, + Optional: true, + Description: "ID of OAuth App associated with the VMC organization", + DefaultFunc: schema.EnvDefaultFunc("NSXT_VMC_CLIENT_ID", nil), + ConflictsWith: []string{"vmc_token"}, + RequiredWith: []string{"vmc_client_secret"}, + }, + "vmc_client_secret": { + Type: schema.TypeString, + Optional: true, + Description: "Secret of OAuth App associated with the VMC organization", + DefaultFunc: schema.EnvDefaultFunc("NSXT_VMC_CLIENT_SECRET", nil), + ConflictsWith: []string{"vmc_token"}, + RequiredWith: []string{"vmc_client_id"}, }, "vmc_auth_mode": { Type: schema.TypeString, @@ -189,7 +206,7 @@ func Provider() *schema.Provider { Type: schema.TypeList, Optional: true, Description: "license keys", - ConflictsWith: []string{"vmc_token"}, + ConflictsWith: []string{"vmc_token", "vmc_client_id", "vmc_client_secret"}, Elem: &schema.Schema{ Type: schema.TypeString, ValidateFunc: validation.StringMatch( @@ -448,13 +465,29 @@ func Provider() *schema.Provider { } } +func isVMCCredentialSet(d *schema.ResourceData) bool { + // Refresh token + vmcToken := d.Get("vmc_token").(string) + if len(vmcToken) > 0 { + return true + } + + // Oauth app + vmcClientId := d.Get("vmc_client_id").(string) + vmcClientSecret := d.Get("vmc_client_secret").(string) + if len(vmcClientSecret) > 0 && len(vmcClientId) > 0 { + return true + } + + return false +} + func configureNsxtClient(d *schema.ResourceData, clients *nsxtClients) error { onDemandConn := d.Get("on_demand_connection").(bool) clientAuthCertFile := d.Get("client_auth_cert_file").(string) clientAuthKeyFile := d.Get("client_auth_key_file").(string) clientAuthCert := d.Get("client_auth_cert").(string) clientAuthKey := d.Get("client_auth_key").(string) - vmcToken := d.Get("vmc_token").(string) vmcAuthMode := d.Get("vmc_auth_mode").(string) if onDemandConn { @@ -462,7 +495,7 @@ func configureNsxtClient(d *schema.ResourceData, clients *nsxtClients) error { return nil } - if (len(vmcToken) > 0) || (vmcAuthMode == "Basic") { + if (vmcAuthMode == "Basic") || isVMCCredentialSet(d) { // VMC can operate without token with basic auth, however MP API is not // available for cloud admin user return nil @@ -556,10 +589,49 @@ type jwtToken struct { RefreshToken string `json:"refresh_token"` } -func getAPIToken(vmcAuthHost string, vmcAccessToken string) (string, error) { +type vmcAuthInfo struct { + authHost string + authMode string + accessToken string + clientID string + clientSecret string +} + +func getVmcAuthInfo(d *schema.ResourceData) *vmcAuthInfo { + return &vmcAuthInfo{ + authHost: d.Get("vmc_auth_host").(string), + authMode: d.Get("vmc_auth_mode").(string), + accessToken: d.Get("vmc_token").(string), + clientID: d.Get("vmc_client_id").(string), + clientSecret: d.Get("vmc_client_secret").(string), + } +} - payload := strings.NewReader("refresh_token=" + vmcAccessToken) - req, _ := http.NewRequest("POST", "https://"+vmcAuthHost, payload) +func (v *vmcAuthInfo) getAPIToken() (string, error) { + var req *http.Request + + // Access token + if len(v.accessToken) > 0 { + if len(v.authHost) == 0 { + v.authHost = "console.cloud.vmware.com/csp/gateway/am/api/auth/api-tokens/authorize" + } + payload := strings.NewReader("refresh_token=" + v.accessToken) + req, _ = http.NewRequest("POST", "https://"+v.authHost, payload) + } + + // Oauth app + if len(v.clientSecret) > 0 && len(v.clientID) > 0 { + if len(v.authHost) == 0 { + v.authHost = "console.cloud.vmware.com/csp/gateway/am/api/auth/authorize" + } + payload := strings.NewReader("grant_type=client_credentials") + req, _ = http.NewRequest("POST", "https://"+v.authHost, payload) + req.SetBasicAuth(v.clientID, v.clientSecret) + } + + if req == nil { + return "", fmt.Errorf("invalid VMC auth input") + } req.Header.Add("content-type", "application/x-www-form-urlencoded") res, err := http.DefaultClient.Do(req) @@ -570,7 +642,7 @@ func getAPIToken(vmcAuthHost string, vmcAccessToken string) (string, error) { if res.StatusCode != 200 { b, _ := ioutil.ReadAll(res.Body) - return "", fmt.Errorf("Unexpected status code %d trying to get auth token. %s", res.StatusCode, string(b)) + return "", fmt.Errorf("unexpected status code %d trying to get auth token. %s", res.StatusCode, string(b)) } defer res.Body.Close() @@ -657,17 +729,15 @@ func configurePolicyConnectorData(d *schema.ResourceData, clients *nsxtClients) host := d.Get("host").(string) username := d.Get("username").(string) password := d.Get("password").(string) - vmcAccessToken := d.Get("vmc_token").(string) - vmcAuthHost := d.Get("vmc_auth_host").(string) clientAuthCertFile := d.Get("client_auth_cert_file").(string) clientAuthCert := d.Get("client_auth_cert").(string) clientAuthDefined := (len(clientAuthCertFile) > 0) || (len(clientAuthCert) > 0) policyEnforcementPoint := d.Get("enforcement_point").(string) policyGlobalManager := d.Get("global_manager").(bool) - vmcAuthMode := d.Get("vmc_auth_mode").(string) + vmcInfo := getVmcAuthInfo(d) isVMC := false - if (len(vmcAccessToken) > 0) || (vmcAuthMode == "Basic") { + if (vmcInfo.authMode == "Basic") || isVMCCredentialSet(d) { isVMC = true if onDemandConn { return fmt.Errorf("on demand connection option is not supported with VMC") @@ -687,7 +757,7 @@ func configurePolicyConnectorData(d *schema.ResourceData, clients *nsxtClients) securityContextNeeded = false } if securityContextNeeded { - securityCtx, err := getConfiguredSecurityContext(clients, vmcAccessToken, vmcAuthHost, vmcAuthMode, username, password) + securityCtx, err := getConfiguredSecurityContext(clients, vmcInfo, username, password) if err != nil { return err } @@ -732,14 +802,22 @@ func configurePolicyConnectorData(d *schema.ResourceData, clients *nsxtClients) return err } -func getConfiguredSecurityContext(clients *nsxtClients, vmcAccessToken string, vmcAuthHost string, vmcAuthMode string, username string, password string) (*core.SecurityContextImpl, error) { +func getConfiguredSecurityContext(clients *nsxtClients, vmcInfo *vmcAuthInfo, username string, password string) (*core.SecurityContextImpl, error) { securityCtx := core.NewSecurityContextImpl() - if len(vmcAccessToken) > 0 { - if vmcAuthHost == "" { - return nil, fmt.Errorf("vmc auth host must be provided if auth token is provided") + if vmcInfo == nil { + if username == "" { + return nil, fmt.Errorf("username must be provided") } - apiToken, err := getAPIToken(vmcAuthHost, vmcAccessToken) + if password == "" { + return nil, fmt.Errorf("password must be provided") + } + + securityCtx.SetProperty(security.AUTHENTICATION_SCHEME_ID, security.USER_PASSWORD_SCHEME_ID) + securityCtx.SetProperty(security.USER_KEY, username) + securityCtx.SetProperty(security.PASSWORD_KEY, password) + } else { + apiToken, err := vmcInfo.getAPIToken() if err != nil { return nil, err } @@ -747,22 +825,10 @@ func getConfiguredSecurityContext(clients *nsxtClients, vmcAccessToken string, v // We'll be sending Bearer token anyway even with scp-auth-token auth // For now, node API is not working on VMC without Bearer token present clients.CommonConfig.BearerToken = apiToken - if vmcAuthMode != "Bearer" { + if vmcInfo.authMode != "Bearer" { securityCtx.SetProperty(security.AUTHENTICATION_SCHEME_ID, security.OAUTH_SCHEME_ID) securityCtx.SetProperty(security.ACCESS_TOKEN, apiToken) } - } else { - if username == "" { - return nil, fmt.Errorf("username must be provided") - } - - if password == "" { - return nil, fmt.Errorf("password must be provided") - } - - securityCtx.SetProperty(security.AUTHENTICATION_SCHEME_ID, security.USER_PASSWORD_SCHEME_ID) - securityCtx.SetProperty(security.USER_KEY, username) - securityCtx.SetProperty(security.PASSWORD_KEY, password) } return securityCtx, nil } diff --git a/nsxt/resource_nsxt_manager_cluster.go b/nsxt/resource_nsxt_manager_cluster.go index 623d919ec..f1e595f03 100644 --- a/nsxt/resource_nsxt_manager_cluster.go +++ b/nsxt/resource_nsxt_manager_cluster.go @@ -324,7 +324,7 @@ func getNewNsxtClient(node NsxClusterNode, d *schema.ResourceData, clients inter func configureNewClient(newClient *nsxtClients, oldClient *nsxtClients, host string, username string, password string) error { newClient.Host = host - securityCtx, err := getConfiguredSecurityContext(newClient, "", "", "", username, password) + securityCtx, err := getConfiguredSecurityContext(newClient, nil, username, password) if err != nil { return fmt.Errorf("Failed to configure new client with host %s: %s", host, err) } diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index efea6c71d..563134d72 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -221,7 +221,15 @@ The following arguments are used to configure the VMware NSX-T Provider: partially successful realization as valid state and not fail apply. * `vmc_token` - (Optional) Long-lived API token for authenticating with VMware Cloud Services APIs. This token will be used to short-lived token that is - needed to communicate with NSX Manager in VMC environment. + needed to communicate with NSX Manager in VMC environment. In conflict with + `vmc_client_id` and `vmc_client_secret`. + Note that only subset of policy resources are supported with VMC environment. +* `vmc_client_id`- (Optional) ID of OAuth App associated with the VMC organization. + The combination with `vmc_client_secret` is used to authenticate when calling + VMware Cloud Services APIs. In conflict with `vmc_token`. +* `vmc_client_secret` - (Optional) Secret of OAuth App associated with the VMC + organization. The combination with `vmc_client_id` is used to authenticate when + calling VMware Cloud Services APIs. In conflict with `vmc_token`. Note that only subset of policy resources are supported with VMC environment. * `vmc_auth_host` - (Optional) URL for VMC authorization service that is used to obtain short-lived token for NSX manager access. Defaults to VMC