|
|
@ -25,7 +25,6 @@ Visit https://github.com/rudecs/terraform-provider-decort for full source code p
|
|
|
|
package decort
|
|
|
|
package decort
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
import (
|
|
|
|
|
|
|
|
|
|
|
|
"bytes"
|
|
|
|
"bytes"
|
|
|
|
"crypto/tls"
|
|
|
|
"crypto/tls"
|
|
|
|
"fmt"
|
|
|
|
"fmt"
|
|
|
@ -34,6 +33,7 @@ import (
|
|
|
|
"net/url"
|
|
|
|
"net/url"
|
|
|
|
"strconv"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
|
|
// "time"
|
|
|
|
// "time"
|
|
|
|
|
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
@ -42,30 +42,29 @@ import (
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
|
|
|
|
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
|
|
|
|
// "github.com/hashicorp/terraform-plugin-sdk/terraform"
|
|
|
|
// "github.com/hashicorp/terraform-plugin-sdk/terraform"
|
|
|
|
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// enumerated constants that define authentication modes
|
|
|
|
// enumerated constants that define authentication modes
|
|
|
|
const (
|
|
|
|
const (
|
|
|
|
MODE_UNDEF = iota // this is the invalid mode - it should never be seen
|
|
|
|
MODE_UNDEF = iota // this is the invalid mode - it should never be seen
|
|
|
|
MODE_LEGACY = iota
|
|
|
|
MODE_LEGACY = iota
|
|
|
|
MODE_OAUTH2 = iota
|
|
|
|
MODE_OAUTH2 = iota
|
|
|
|
MODE_JWT = iota
|
|
|
|
MODE_JWT = iota
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type ControllerCfg struct {
|
|
|
|
type ControllerCfg struct {
|
|
|
|
controller_url string // always required
|
|
|
|
controller_url string // always required
|
|
|
|
auth_mode_code int // always required
|
|
|
|
auth_mode_code int // always required
|
|
|
|
auth_mode_txt string // always required, it is a text representation of auth mode
|
|
|
|
auth_mode_txt string // always required, it is a text representation of auth mode
|
|
|
|
legacy_user string // required for legacy mode
|
|
|
|
legacy_user string // required for legacy mode
|
|
|
|
legacy_password string // required for legacy mode
|
|
|
|
legacy_password string // required for legacy mode
|
|
|
|
legacy_sid string // obtained from DECORT controller on successful login in legacy mode
|
|
|
|
legacy_sid string // obtained from DECORT controller on successful login in legacy mode
|
|
|
|
jwt string // obtained from Outh2 provider on successful login in oauth2 mode, required in jwt mode
|
|
|
|
jwt string // obtained from Outh2 provider on successful login in oauth2 mode, required in jwt mode
|
|
|
|
app_id string // required for oauth2 mode
|
|
|
|
app_id string // required for oauth2 mode
|
|
|
|
app_secret string // required for oauth2 mode
|
|
|
|
app_secret string // required for oauth2 mode
|
|
|
|
oauth2_url string // always required
|
|
|
|
oauth2_url string // always required
|
|
|
|
decort_username string // assigned to either legacy_user (legacy mode) or Oauth2 user (oauth2 mode) upon successful verification
|
|
|
|
decort_username string // assigned to either legacy_user (legacy mode) or Oauth2 user (oauth2 mode) upon successful verification
|
|
|
|
cc_client *http.Client // assigned when all initial checks successfully passed
|
|
|
|
cc_client *http.Client // assigned when all initial checks successfully passed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
|
|
|
|
func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
|
|
|
@ -90,7 +89,7 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
|
|
|
|
app_id: d.Get("app_id").(string),
|
|
|
|
app_id: d.Get("app_id").(string),
|
|
|
|
app_secret: d.Get("app_secret").(string),
|
|
|
|
app_secret: d.Get("app_secret").(string),
|
|
|
|
oauth2_url: d.Get("oauth2_url").(string),
|
|
|
|
oauth2_url: d.Get("oauth2_url").(string),
|
|
|
|
decort_username: "",
|
|
|
|
decort_username: "",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var allow_unverified_ssl bool
|
|
|
|
var allow_unverified_ssl bool
|
|
|
@ -137,10 +136,10 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
|
|
|
|
|
|
|
|
|
|
|
|
if allow_unverified_ssl {
|
|
|
|
if allow_unverified_ssl {
|
|
|
|
log.Warn("ControllerConfigure: allow_unverified_ssl is set - will not check certificates!")
|
|
|
|
log.Warn("ControllerConfigure: allow_unverified_ssl is set - will not check certificates!")
|
|
|
|
transCfg := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true},}
|
|
|
|
transCfg := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
|
|
|
|
ret_config.cc_client = &http.Client{
|
|
|
|
ret_config.cc_client = &http.Client{
|
|
|
|
Transport: transCfg,
|
|
|
|
Transport: transCfg,
|
|
|
|
Timeout: Timeout180s,
|
|
|
|
Timeout: Timeout180s,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
ret_config.cc_client = &http.Client{
|
|
|
|
ret_config.cc_client = &http.Client{
|
|
|
@ -181,7 +180,7 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
|
|
|
|
tbuf.WriteString(claims["username"].(string))
|
|
|
|
tbuf.WriteString(claims["username"].(string))
|
|
|
|
tbuf.WriteString("@")
|
|
|
|
tbuf.WriteString("@")
|
|
|
|
tbuf.WriteString(claims["iss"].(string))
|
|
|
|
tbuf.WriteString(claims["iss"].(string))
|
|
|
|
ret_config.decort_username = tbuf.String()
|
|
|
|
ret_config.decort_username = tbuf.String()
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
return nil, fmt.Errorf("Failed to extract user and iss fields from JWT token in oauth2 mode.")
|
|
|
|
return nil, fmt.Errorf("Failed to extract user and iss fields from JWT token in oauth2 mode.")
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -195,7 +194,7 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
|
|
|
|
return ret_config, nil
|
|
|
|
return ret_config, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (config *ControllerCfg) getDecortUsername() (string) {
|
|
|
|
func (config *ControllerCfg) getDecortUsername() string {
|
|
|
|
return config.decort_username
|
|
|
|
return config.decort_username
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -216,7 +215,7 @@ func (config *ControllerCfg) getOAuth2JWT() (string, error) {
|
|
|
|
params.Add("validity", "3600")
|
|
|
|
params.Add("validity", "3600")
|
|
|
|
params_str := params.Encode()
|
|
|
|
params_str := params.Encode()
|
|
|
|
|
|
|
|
|
|
|
|
req, err := http.NewRequest("POST", config.oauth2_url + "/v1/oauth/access_token", strings.NewReader(params_str))
|
|
|
|
req, err := http.NewRequest("POST", config.oauth2_url+"/v1/oauth/access_token", strings.NewReader(params_str))
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -232,14 +231,14 @@ func (config *ControllerCfg) getOAuth2JWT() (string, error) {
|
|
|
|
// fmt.Println("response Headers:", resp.Header)
|
|
|
|
// fmt.Println("response Headers:", resp.Header)
|
|
|
|
// fmt.Println("response Headers:", req.URL)
|
|
|
|
// fmt.Println("response Headers:", req.URL)
|
|
|
|
return "", fmt.Errorf("getOauth2JWT: unexpected status code %d when obtaining JWT from %q for APP_ID %q, request Body %q",
|
|
|
|
return "", fmt.Errorf("getOauth2JWT: unexpected status code %d when obtaining JWT from %q for APP_ID %q, request Body %q",
|
|
|
|
resp.StatusCode, req.URL, config.app_id, params_str)
|
|
|
|
resp.StatusCode, req.URL, config.app_id, params_str)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
|
|
responseData, err := ioutil.ReadAll(resp.Body)
|
|
|
|
responseData, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// validation successful - store JWT in the corresponding field of the ControllerCfg structure
|
|
|
|
// validation successful - store JWT in the corresponding field of the ControllerCfg structure
|
|
|
|
config.jwt = strings.TrimSpace(string(responseData))
|
|
|
|
config.jwt = strings.TrimSpace(string(responseData))
|
|
|
@ -249,10 +248,10 @@ func (config *ControllerCfg) getOAuth2JWT() (string, error) {
|
|
|
|
|
|
|
|
|
|
|
|
func (config *ControllerCfg) validateJWT(jwt string) (bool, error) {
|
|
|
|
func (config *ControllerCfg) validateJWT(jwt string) (bool, error) {
|
|
|
|
/*
|
|
|
|
/*
|
|
|
|
Validate JWT against DECORT controller. JWT can be supplied as argument to this method. If empty string supplied as
|
|
|
|
Validate JWT against DECORT controller. JWT can be supplied as argument to this method. If empty string supplied as
|
|
|
|
argument, JWT will be taken from config attribute.
|
|
|
|
argument, JWT will be taken from config attribute.
|
|
|
|
DECORT controller URL will always be taken from the config attribute assigned at instantiation.
|
|
|
|
DECORT controller URL will always be taken from the config attribute assigned at instantiation.
|
|
|
|
Validation is accomplished by attempting API call that lists accounts for the invoking user.
|
|
|
|
Validation is accomplished by attempting API call that lists accounts for the invoking user.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
if jwt == "" {
|
|
|
|
if jwt == "" {
|
|
|
|
if config.jwt == "" {
|
|
|
|
if config.jwt == "" {
|
|
|
@ -265,7 +264,7 @@ func (config *ControllerCfg) validateJWT(jwt string) (bool, error) {
|
|
|
|
return false, fmt.Errorf("validateJWT method called, but no OAuth2 URL provided.")
|
|
|
|
return false, fmt.Errorf("validateJWT method called, but no OAuth2 URL provided.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
req, err := http.NewRequest("POST", config.controller_url + "/restmachine/cloudapi/accounts/list", nil)
|
|
|
|
req, err := http.NewRequest("POST", config.controller_url+"/restmachine/cloudapi/accounts/list", nil)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -280,7 +279,7 @@ func (config *ControllerCfg) validateJWT(jwt string) (bool, error) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return false, fmt.Errorf("validateJWT: unexpected status code %d when validating JWT against %q.",
|
|
|
|
return false, fmt.Errorf("validateJWT: unexpected status code %d when validating JWT against %q.",
|
|
|
|
resp.StatusCode, req.URL)
|
|
|
|
resp.StatusCode, req.URL)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
|
@ -289,10 +288,10 @@ func (config *ControllerCfg) validateJWT(jwt string) (bool, error) {
|
|
|
|
|
|
|
|
|
|
|
|
func (config *ControllerCfg) validateLegacyUser() (bool, error) {
|
|
|
|
func (config *ControllerCfg) validateLegacyUser() (bool, error) {
|
|
|
|
/*
|
|
|
|
/*
|
|
|
|
Validate legacy user by obtaining a session key, which will be used for authenticating subsequent API calls
|
|
|
|
Validate legacy user by obtaining a session key, which will be used for authenticating subsequent API calls
|
|
|
|
to DECORT controller.
|
|
|
|
to DECORT controller.
|
|
|
|
If successful, the session key is stored in config.legacy_sid and true is returned. If unsuccessful for any
|
|
|
|
If successful, the session key is stored in config.legacy_sid and true is returned. If unsuccessful for any
|
|
|
|
reason, the method will return false and error.
|
|
|
|
reason, the method will return false and error.
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
if config.auth_mode_code == MODE_UNDEF {
|
|
|
|
if config.auth_mode_code == MODE_UNDEF {
|
|
|
|
return false, fmt.Errorf("validateLegacyUser method called for undefined authorization mode.")
|
|
|
|
return false, fmt.Errorf("validateLegacyUser method called for undefined authorization mode.")
|
|
|
@ -306,7 +305,7 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) {
|
|
|
|
params.Add("password", config.legacy_password)
|
|
|
|
params.Add("password", config.legacy_password)
|
|
|
|
params_str := params.Encode()
|
|
|
|
params_str := params.Encode()
|
|
|
|
|
|
|
|
|
|
|
|
req, err := http.NewRequest("POST", config.controller_url + "/restmachine/cloudapi/users/authenticate", strings.NewReader(params_str))
|
|
|
|
req, err := http.NewRequest("POST", config.controller_url+"/restmachine/cloudapi/users/authenticate", strings.NewReader(params_str))
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -320,14 +319,14 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return false, fmt.Errorf("validateLegacyUser: unexpected status code %d when validating legacy user %q against %q.",
|
|
|
|
return false, fmt.Errorf("validateLegacyUser: unexpected status code %d when validating legacy user %q against %q.",
|
|
|
|
resp.StatusCode, config.legacy_user, config.controller_url)
|
|
|
|
resp.StatusCode, config.legacy_user, config.controller_url)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
|
|
responseData, err := ioutil.ReadAll(resp.Body)
|
|
|
|
responseData, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// validation successful - keep session ID for future use
|
|
|
|
// validation successful - keep session ID for future use
|
|
|
|
config.legacy_sid = string(responseData)
|
|
|
|
config.legacy_sid = string(responseData)
|
|
|
@ -367,12 +366,13 @@ func (config *ControllerCfg) decortAPICall(method string, api_name string, url_v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
params_str := url_values.Encode()
|
|
|
|
params_str := url_values.Encode()
|
|
|
|
|
|
|
|
|
|
|
|
req, err := http.NewRequest(method, config.controller_url + api_name, strings.NewReader(params_str))
|
|
|
|
req, err := http.NewRequest(method, config.controller_url+api_name, strings.NewReader(params_str))
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
|
|
req.Header.Set("Content-Length", strconv.Itoa(len(params_str)))
|
|
|
|
req.Header.Set("Content-Length", strconv.Itoa(len(params_str)))
|
|
|
|
|
|
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
|
|
|
|
|
|
|
|
if config.auth_mode_code == MODE_OAUTH2 || config.auth_mode_code == MODE_JWT {
|
|
|
|
if config.auth_mode_code == MODE_OAUTH2 || config.auth_mode_code == MODE_JWT {
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("bearer %s", config.jwt))
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("bearer %s", config.jwt))
|
|
|
@ -384,25 +384,23 @@ func (config *ControllerCfg) decortAPICall(method string, api_name string, url_v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
|
|
if resp.StatusCode == http.StatusOK {
|
|
|
|
if resp.StatusCode == http.StatusOK {
|
|
|
|
tmp_body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
json_resp := Jo2JSON(string(tmp_body))
|
|
|
|
log.Debugf("decortAPICall: %s %s\n %s", method, api_name, body)
|
|
|
|
log.Debugf("decortAPICall: %s %s\n %s", method, api_name, json_resp)
|
|
|
|
return string(body), nil
|
|
|
|
return json_resp, nil
|
|
|
|
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
return "", fmt.Errorf("decortAPICall: unexpected status code %d when calling API %q with request Body %q",
|
|
|
|
return "", fmt.Errorf("decortAPICall: unexpected status code %d when calling API %q with request Body %q",
|
|
|
|
resp.StatusCode, req.URL, params_str)
|
|
|
|
resp.StatusCode, req.URL, params_str)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
/*
|
|
|
|
if resp.StatusCode == StatusServiceUnavailable {
|
|
|
|
if resp.StatusCode == StatusServiceUnavailable {
|
|
|
|
return nil, fmt.Errorf("decortAPICall method called for incompatible authorization mode %q.", config.auth_mode_txt)
|
|
|
|
return nil, fmt.Errorf("decortAPICall method called for incompatible authorization mode %q.", config.auth_mode_txt)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
return "", err
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|