|
|
|
@ -1,5 +1,5 @@
|
|
|
|
|
/*
|
|
|
|
|
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
|
|
|
|
|
Copyright (c) 2019-2023 Digital Energy Cloud Solutions LLC. All Rights Reserved.
|
|
|
|
|
Authors:
|
|
|
|
|
Petr Krutov, <petr.krutov@digitalenergy.online>
|
|
|
|
|
Stanislav Solovev, <spsolovev@digitalenergy.online>
|
|
|
|
@ -24,15 +24,15 @@ import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"crypto/tls"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"io"
|
|
|
|
|
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/url"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
// "time"
|
|
|
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
|
"golang.org/x/oauth2"
|
|
|
|
|
decort "repository.basistech.ru/BASIS/decort-golang-sdk"
|
|
|
|
|
"repository.basistech.ru/BASIS/decort-golang-sdk/config"
|
|
|
|
|
"repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
|
|
|
|
@ -46,24 +46,29 @@ import (
|
|
|
|
|
|
|
|
|
|
// enumerated constants that define authentication modes
|
|
|
|
|
const (
|
|
|
|
|
MODE_UNDEF = iota // this is the invalid mode - it should never be seen
|
|
|
|
|
MODE_LEGACY = iota
|
|
|
|
|
MODE_OAUTH2 = iota
|
|
|
|
|
MODE_JWT = iota
|
|
|
|
|
MODE_UNDEF = iota // this is the invalid mode - it should never be seen
|
|
|
|
|
MODE_LEGACY
|
|
|
|
|
MODE_DECS3O
|
|
|
|
|
MODE_JWT
|
|
|
|
|
MODE_BVS
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type ControllerCfg struct {
|
|
|
|
|
controller_url string // always required
|
|
|
|
|
auth_mode_code int // always required
|
|
|
|
|
auth_mode_txt string // always required, it is a text representation of auth mode
|
|
|
|
|
bvs_user string // required for bvs mode
|
|
|
|
|
bvs_password string // required for bvs mode
|
|
|
|
|
domain string // required for bvs mode
|
|
|
|
|
token oauth2.Token // obtained from BVS provider on successful login in bvs mode
|
|
|
|
|
legacy_user 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
|
|
|
|
|
jwt string // obtained from Outh2 provider on successful login in oauth2 mode, required in jwt mode
|
|
|
|
|
app_id string // required for oauth2 mode
|
|
|
|
|
app_secret string // required for oauth2 mode
|
|
|
|
|
oauth2_url string // always required
|
|
|
|
|
decort_username string // assigned to either legacy_user (legacy mode) or Oauth2 user (oauth2 mode) upon successful verification
|
|
|
|
|
jwt string // obtained from Outh2 provider on successful login in decs3o mode, required in jwt mode
|
|
|
|
|
app_id string // required for decs3o and bvs mode
|
|
|
|
|
app_secret string // required for decs3o and bvs mode
|
|
|
|
|
oauth2_url string // required for decs3o and bvs mode
|
|
|
|
|
decort_username string // assigned to either legacy_user (legacy mode) or Oauth2 user (decs3o mode) upon successful verification
|
|
|
|
|
cc_client *http.Client // assigned when all initial checks successfully passed
|
|
|
|
|
caller interfaces.Caller
|
|
|
|
|
}
|
|
|
|
@ -86,17 +91,21 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
|
|
|
|
|
legacy_user: d.Get("user").(string),
|
|
|
|
|
legacy_password: d.Get("password").(string),
|
|
|
|
|
legacy_sid: "",
|
|
|
|
|
bvs_user: d.Get("bvs_user").(string),
|
|
|
|
|
bvs_password: d.Get("bvs_password").(string),
|
|
|
|
|
domain: d.Get("domain").(string),
|
|
|
|
|
jwt: d.Get("jwt").(string),
|
|
|
|
|
app_id: d.Get("app_id").(string),
|
|
|
|
|
app_secret: d.Get("app_secret").(string),
|
|
|
|
|
oauth2_url: d.Get("oauth2_url").(string),
|
|
|
|
|
decort_username: "",
|
|
|
|
|
token: oauth2.Token{},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
allow_unverified_ssl := d.Get("allow_unverified_ssl").(bool)
|
|
|
|
|
|
|
|
|
|
if ret_config.controller_url == "" {
|
|
|
|
|
return nil, fmt.Errorf("Empty DECORT cloud controller URL provided.")
|
|
|
|
|
return nil, fmt.Errorf("empty DECORT cloud controller URL provided")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// this should have already been done by StateFunc defined in Schema, but we want to be sure
|
|
|
|
@ -105,33 +114,53 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
|
|
|
|
|
switch ret_config.auth_mode_txt {
|
|
|
|
|
case "jwt":
|
|
|
|
|
if ret_config.jwt == "" {
|
|
|
|
|
return nil, fmt.Errorf("Authenticator mode 'jwt' specified but no JWT provided.")
|
|
|
|
|
return nil, fmt.Errorf("authenticator mode 'jwt' specified but no JWT provided")
|
|
|
|
|
}
|
|
|
|
|
ret_config.auth_mode_code = MODE_JWT
|
|
|
|
|
case "oauth2":
|
|
|
|
|
case "decs3o":
|
|
|
|
|
if ret_config.oauth2_url == "" {
|
|
|
|
|
return nil, fmt.Errorf("Authenticator mode 'oauth2' specified but no OAuth2 URL provided.")
|
|
|
|
|
return nil, fmt.Errorf("authenticator mode 'decs3o' specified but no OAuth2 URL provided")
|
|
|
|
|
}
|
|
|
|
|
if ret_config.app_id == "" {
|
|
|
|
|
return nil, fmt.Errorf("Authenticator mode 'oauth2' specified but no Application ID provided.")
|
|
|
|
|
return nil, fmt.Errorf("authenticator mode 'decs3o' specified but no Application ID provided")
|
|
|
|
|
}
|
|
|
|
|
if ret_config.app_secret == "" {
|
|
|
|
|
return nil, fmt.Errorf("Authenticator mode 'oauth2' specified but no Secret ID provided.")
|
|
|
|
|
return nil, fmt.Errorf("authenticator mode 'decs3o' specified but no Secret ID provided")
|
|
|
|
|
}
|
|
|
|
|
ret_config.auth_mode_code = MODE_OAUTH2
|
|
|
|
|
ret_config.auth_mode_code = MODE_DECS3O
|
|
|
|
|
case "legacy":
|
|
|
|
|
//
|
|
|
|
|
ret_config.legacy_user = d.Get("user").(string)
|
|
|
|
|
if ret_config.legacy_user == "" {
|
|
|
|
|
return nil, fmt.Errorf("Authenticator mode 'legacy' specified but no user provided.")
|
|
|
|
|
return nil, fmt.Errorf("authenticator mode 'legacy' specified but no user provided")
|
|
|
|
|
}
|
|
|
|
|
ret_config.legacy_password = d.Get("password").(string)
|
|
|
|
|
if ret_config.legacy_password == "" {
|
|
|
|
|
return nil, fmt.Errorf("Authenticator mode 'legacy' specified but no password provided.")
|
|
|
|
|
return nil, fmt.Errorf("authenticator mode 'legacy' specified but no password provided")
|
|
|
|
|
}
|
|
|
|
|
ret_config.auth_mode_code = MODE_LEGACY
|
|
|
|
|
case "bvs":
|
|
|
|
|
if ret_config.bvs_user == "" {
|
|
|
|
|
return nil, fmt.Errorf("authenticator mode 'bvs' specified but no user provided")
|
|
|
|
|
}
|
|
|
|
|
if ret_config.bvs_password == "" {
|
|
|
|
|
return nil, fmt.Errorf("authenticator mode 'bvs' specified but no password provided")
|
|
|
|
|
}
|
|
|
|
|
if ret_config.oauth2_url == "" {
|
|
|
|
|
return nil, fmt.Errorf("authenticator mode 'bvs' specified but no bvs URL provided")
|
|
|
|
|
}
|
|
|
|
|
if ret_config.app_id == "" {
|
|
|
|
|
return nil, fmt.Errorf("authenticator mode 'bvs' specified but no Application ID provided")
|
|
|
|
|
}
|
|
|
|
|
if ret_config.app_secret == "" {
|
|
|
|
|
return nil, fmt.Errorf("authenticator mode 'bvs' specified but no Secret ID provided")
|
|
|
|
|
}
|
|
|
|
|
if ret_config.domain == "" {
|
|
|
|
|
return nil, fmt.Errorf("authenticator mode 'bvs' specified but no Domain provided")
|
|
|
|
|
}
|
|
|
|
|
ret_config.auth_mode_code = MODE_BVS
|
|
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("Unknown authenticator mode %q provided.", ret_config.auth_mode_txt)
|
|
|
|
|
return nil, fmt.Errorf("unknown authenticator mode %q provided", ret_config.auth_mode_txt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if allow_unverified_ssl {
|
|
|
|
@ -167,10 +196,10 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
case MODE_OAUTH2:
|
|
|
|
|
// on success getOAuth2JWT will set config.jwt to the obtained JWT, so there is no
|
|
|
|
|
case MODE_DECS3O:
|
|
|
|
|
// on success getDECS3OJWT will set config.jwt to the obtained JWT, so there is no
|
|
|
|
|
// need to set it once again here
|
|
|
|
|
_, err := ret_config.getOAuth2JWT()
|
|
|
|
|
_, err := ret_config.getDECS3OJWT()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
@ -189,7 +218,7 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
|
|
|
|
|
tbuf.WriteString(claims["iss"].(string))
|
|
|
|
|
ret_config.decort_username = tbuf.String()
|
|
|
|
|
} 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")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sdkConf := config.Config{
|
|
|
|
@ -201,10 +230,24 @@ func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret_config.caller = decort.New(sdkConf)
|
|
|
|
|
case MODE_BVS:
|
|
|
|
|
|
|
|
|
|
sdkConf := config.BVSConfig{
|
|
|
|
|
AppID: ret_config.app_id,
|
|
|
|
|
AppSecret: ret_config.app_secret,
|
|
|
|
|
SSOURL: ret_config.oauth2_url,
|
|
|
|
|
DecortURL: ret_config.controller_url,
|
|
|
|
|
SSLSkipVerify: allow_unverified_ssl,
|
|
|
|
|
Username: ret_config.bvs_user,
|
|
|
|
|
Password: ret_config.bvs_password,
|
|
|
|
|
Domain: ret_config.domain,
|
|
|
|
|
Token: ret_config.token,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret_config.caller = decort.NewBVS(sdkConf)
|
|
|
|
|
default:
|
|
|
|
|
// FYI, this should never happen due to all above checks, but we want to be fool proof
|
|
|
|
|
return nil, fmt.Errorf("Unknown authenticator mode code %d provided.", ret_config.auth_mode_code)
|
|
|
|
|
return nil, fmt.Errorf("unknown authenticator mode code %d provided", ret_config.auth_mode_code)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// All checks passed successfully, credentials corresponding to the selected authenticator mode
|
|
|
|
@ -216,13 +259,13 @@ func (config *ControllerCfg) GetDecortUsername() string {
|
|
|
|
|
return config.decort_username
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (config *ControllerCfg) getOAuth2JWT() (string, error) {
|
|
|
|
|
func (config *ControllerCfg) getDECS3OJWT() (string, error) {
|
|
|
|
|
// Obtain JWT from the Oauth2 provider using application ID and application secret provided in config.
|
|
|
|
|
if config.auth_mode_code == MODE_UNDEF {
|
|
|
|
|
return "", fmt.Errorf("getOAuth2JWT method called for undefined authorization mode.")
|
|
|
|
|
return "", fmt.Errorf("getOAuth2JWT method called for undefined authorization mode")
|
|
|
|
|
}
|
|
|
|
|
if config.auth_mode_code != MODE_OAUTH2 {
|
|
|
|
|
return "", fmt.Errorf("getOAuth2JWT method called for incompatible authorization mode %q.", config.auth_mode_txt)
|
|
|
|
|
if config.auth_mode_code != MODE_DECS3O {
|
|
|
|
|
return "", fmt.Errorf("getOAuth2JWT method called for incompatible authorization mode %q", config.auth_mode_txt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
params := url.Values{}
|
|
|
|
@ -253,7 +296,7 @@ func (config *ControllerCfg) getOAuth2JWT() (string, error) {
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
responseData, err := ioutil.ReadAll(resp.Body)
|
|
|
|
|
responseData, err := io.ReadAll(resp.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
@ -273,13 +316,13 @@ func (config *ControllerCfg) validateJWT(jwt string) (bool, error) {
|
|
|
|
|
*/
|
|
|
|
|
if jwt == "" {
|
|
|
|
|
if config.jwt == "" {
|
|
|
|
|
return false, fmt.Errorf("validateJWT method called, but no meaningful JWT provided.")
|
|
|
|
|
return false, fmt.Errorf("validateJWT method called, but no meaningful JWT provided")
|
|
|
|
|
}
|
|
|
|
|
jwt = config.jwt
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if config.oauth2_url == "" {
|
|
|
|
|
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/account/list", nil)
|
|
|
|
@ -296,7 +339,7 @@ func (config *ControllerCfg) validateJWT(jwt string) (bool, error) {
|
|
|
|
|
return false, err
|
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
@ -312,10 +355,10 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) {
|
|
|
|
|
reason, the method will return false and error.
|
|
|
|
|
*/
|
|
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
if config.auth_mode_code != MODE_LEGACY {
|
|
|
|
|
return false, fmt.Errorf("validateLegacyUser method called for incompatible authorization mode %q.", config.auth_mode_txt)
|
|
|
|
|
return false, fmt.Errorf("validateLegacyUser method called for incompatible authorization mode %q", config.auth_mode_txt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
params := url.Values{}
|
|
|
|
@ -336,12 +379,12 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) {
|
|
|
|
|
return false, err
|
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
responseData, err := ioutil.ReadAll(resp.Body)
|
|
|
|
|
responseData, err := io.ReadAll(req.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
|
|
|
@ -353,21 +396,32 @@ func (config *ControllerCfg) validateLegacyUser() (bool, error) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (config *ControllerCfg) CloudAPI() *cloudapi.CloudAPI {
|
|
|
|
|
if config.auth_mode_code == MODE_LEGACY {
|
|
|
|
|
switch config.auth_mode_code {
|
|
|
|
|
case MODE_LEGACY:
|
|
|
|
|
client, _ := config.caller.(*decort.LegacyDecortClient)
|
|
|
|
|
return client.CloudAPI()
|
|
|
|
|
case MODE_DECS3O:
|
|
|
|
|
client, _ := config.caller.(*decort.DecortClient)
|
|
|
|
|
return client.CloudAPI()
|
|
|
|
|
case MODE_BVS:
|
|
|
|
|
client, _ := config.caller.(*decort.BVSDecortClient)
|
|
|
|
|
return client.CloudAPI()
|
|
|
|
|
default:
|
|
|
|
|
return &cloudapi.CloudAPI{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
client, _ := config.caller.(*decort.DecortClient)
|
|
|
|
|
return client.CloudAPI()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (config *ControllerCfg) CloudBroker() *cloudbroker.CloudBroker {
|
|
|
|
|
if config.auth_mode_code == MODE_LEGACY {
|
|
|
|
|
switch config.auth_mode_code {
|
|
|
|
|
case MODE_LEGACY:
|
|
|
|
|
client, _ := config.caller.(*decort.LegacyDecortClient)
|
|
|
|
|
return client.CloudBroker()
|
|
|
|
|
case MODE_DECS3O:
|
|
|
|
|
client, _ := config.caller.(*decort.DecortClient)
|
|
|
|
|
return client.CloudBroker()
|
|
|
|
|
case MODE_BVS:
|
|
|
|
|
client, _ := config.caller.(*decort.BVSDecortClient)
|
|
|
|
|
return client.CloudBroker()
|
|
|
|
|
default:
|
|
|
|
|
return &cloudbroker.CloudBroker{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
client, _ := config.caller.(*decort.DecortClient)
|
|
|
|
|
return client.CloudBroker()
|
|
|
|
|
}
|
|
|
|
|