This commit is contained in:
Nikita Sorokin
2023-11-13 00:59:02 +03:00
parent 2bc0fbae9a
commit 294680282e
158 changed files with 362 additions and 280 deletions

View File

@@ -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()
}