git test
This commit is contained in:
@@ -1,449 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2019-2023 Digital Energy Cloud Solutions LLC. All Rights Reserved.
|
||||
Authors:
|
||||
Petr Krutov, <petr.krutov@digitalenergy.online>
|
||||
Stanislav Solovev, <spsolovev@digitalenergy.online>
|
||||
Kasim Baybikov, <kmbaybikov@basistech.ru>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
decort "repository.basistech.ru/BASIS/decort-golang-sdk"
|
||||
"repository.basistech.ru/BASIS/decort-golang-sdk/config"
|
||||
"repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
|
||||
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi"
|
||||
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker"
|
||||
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
// enumerated constants that define authentication modes
|
||||
const (
|
||||
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 config.Token // obtained from BVS provider on successful login in bvs mode
|
||||
path_cfg string // the path of the configuration file entry
|
||||
path_token string // the path of the token file entry
|
||||
time_to_refresh int64 // the number of minutes before the expiration of the token, a refresh will be made
|
||||
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 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
|
||||
}
|
||||
|
||||
func ControllerConfigure(d *schema.ResourceData) (*ControllerCfg, error) {
|
||||
// This function first will check that all required provider parameters for the
|
||||
// selected authenticator mode are set correctly and initialize ControllerCfg structure
|
||||
// based on the provided parameters.
|
||||
//
|
||||
// Next, it will check for validity of supplied credentials by initiating connection to the specified
|
||||
// DECORT controller URL and, if succeeded, completes ControllerCfg structure with the rest of computed
|
||||
// parameters (e.g. JWT, session ID and Oauth2 user name).
|
||||
//
|
||||
// The structure created by this function should be used with subsequent calls to decortAPICall() method,
|
||||
// which is a DECORT authentication mode aware wrapper around standard HTTP requests.
|
||||
|
||||
ret_config := &ControllerCfg{
|
||||
controller_url: d.Get("controller_url").(string),
|
||||
auth_mode_code: MODE_UNDEF,
|
||||
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: config.Token{},
|
||||
path_cfg: d.Get("path_cfg").(string),
|
||||
path_token: d.Get("path_token").(string),
|
||||
time_to_refresh: int64(d.Get("time_to_refresh").(int)),
|
||||
}
|
||||
|
||||
allow_unverified_ssl := d.Get("allow_unverified_ssl").(bool)
|
||||
|
||||
if ret_config.controller_url == "" {
|
||||
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
|
||||
ret_config.auth_mode_txt = strings.ToLower(d.Get("authenticator").(string))
|
||||
|
||||
switch ret_config.auth_mode_txt {
|
||||
case "jwt":
|
||||
if ret_config.jwt == "" {
|
||||
return nil, fmt.Errorf("authenticator mode 'jwt' specified but no JWT provided")
|
||||
}
|
||||
ret_config.auth_mode_code = MODE_JWT
|
||||
case "decs3o":
|
||||
if ret_config.oauth2_url == "" {
|
||||
return nil, fmt.Errorf("authenticator mode 'decs3o' specified but no OAuth2 URL provided")
|
||||
}
|
||||
if ret_config.app_id == "" {
|
||||
return nil, fmt.Errorf("authenticator mode 'decs3o' specified but no Application ID provided")
|
||||
}
|
||||
if ret_config.app_secret == "" {
|
||||
return nil, fmt.Errorf("authenticator mode 'decs3o' specified but no Secret ID provided")
|
||||
}
|
||||
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")
|
||||
}
|
||||
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")
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
if allow_unverified_ssl {
|
||||
log.Warn("ControllerConfigure: allow_unverified_ssl is set - will not check certificates!")
|
||||
transCfg := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} //nolint:gosec
|
||||
ret_config.cc_client = &http.Client{
|
||||
Transport: transCfg,
|
||||
}
|
||||
} else {
|
||||
ret_config.cc_client = &http.Client{}
|
||||
}
|
||||
|
||||
switch ret_config.auth_mode_code {
|
||||
case MODE_LEGACY:
|
||||
ok, err := ret_config.validateLegacyUser()
|
||||
if !ok {
|
||||
return nil, err
|
||||
}
|
||||
ret_config.decort_username = ret_config.legacy_user
|
||||
|
||||
sdkConf := config.LegacyConfig{
|
||||
Username: ret_config.legacy_user,
|
||||
Password: ret_config.legacy_password,
|
||||
DecortURL: ret_config.controller_url,
|
||||
SSLSkipVerify: allow_unverified_ssl,
|
||||
}
|
||||
|
||||
ret_config.caller = decort.NewLegacy(sdkConf)
|
||||
|
||||
case MODE_JWT:
|
||||
//
|
||||
ok, err := ret_config.validateJWT("")
|
||||
if !ok {
|
||||
return nil, err
|
||||
}
|
||||
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.getDECS3OJWT()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// we are not verifying the JWT when parsing because actual verification is done on the
|
||||
// OVC controller side. Here we do parsing solely to extract Oauth2 user name (claim "user")
|
||||
// and JWT issuer name (claim "iss")
|
||||
// parser := jwt.Parser{}
|
||||
// token, _, err := parser.ParseUnverified(ret_config.jwt, jwt.MapClaims{})
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
||||
// var tbuf bytes.Buffer
|
||||
// tbuf.WriteString(claims["username"].(string))
|
||||
// tbuf.WriteString("@")
|
||||
// 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")
|
||||
// }
|
||||
|
||||
sdkConf := config.Config{
|
||||
AppID: ret_config.app_id,
|
||||
AppSecret: ret_config.app_secret,
|
||||
SSOURL: ret_config.oauth2_url,
|
||||
DecortURL: ret_config.controller_url,
|
||||
SSLSkipVerify: allow_unverified_ssl,
|
||||
}
|
||||
|
||||
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,
|
||||
PathCfg: ret_config.path_cfg,
|
||||
PathToken: ret_config.path_token,
|
||||
TimeToRefresh: ret_config.time_to_refresh,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// All checks passed successfully, credentials corresponding to the selected authenticator mode
|
||||
// obtained and validated.
|
||||
return ret_config, nil
|
||||
}
|
||||
|
||||
// func (config *ControllerCfg) GetDecortUsername() string {
|
||||
// return config.decort_username
|
||||
// }
|
||||
|
||||
// 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")
|
||||
// }
|
||||
// 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{}
|
||||
// params.Add("grant_type", "client_credentials")
|
||||
// params.Add("client_id", config.app_id)
|
||||
// params.Add("client_secret", config.app_secret)
|
||||
// params.Add("response_type", "id_token")
|
||||
// params.Add("validity", "3600")
|
||||
// params_str := params.Encode()
|
||||
|
||||
// req, err := http.NewRequest("POST", config.oauth2_url+"/v1/oauth/access_token", strings.NewReader(params_str))
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
// req.Header.Set("Content-Length", strconv.Itoa(len(params_str)))
|
||||
|
||||
// resp, err := config.cc_client.Do(req)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// if resp.StatusCode != http.StatusOK {
|
||||
// // fmt.Println("response Status:", resp.Status)
|
||||
// // fmt.Println("response Headers:", resp.Header)
|
||||
// // 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",
|
||||
// resp.StatusCode, req.URL, config.app_id, params_str)
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
|
||||
// responseData, err := io.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
|
||||
// // validation successful - store JWT in the corresponding field of the ControllerCfg structure
|
||||
// config.jwt = strings.TrimSpace(string(responseData))
|
||||
|
||||
// return config.jwt, nil
|
||||
// }
|
||||
|
||||
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
|
||||
argument, JWT will be taken from config attribute.
|
||||
DECORT controller URL will always be taken from the config attribute assigned at instantiation.
|
||||
Validation is accomplished by attempting API call that lists account for the invoking user.
|
||||
*/
|
||||
if jwt == "" {
|
||||
if config.jwt == "" {
|
||||
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")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", config.controller_url+"/restmachine/cloudapi/account/list", nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("bearer %s", jwt))
|
||||
// req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
// req.Header.Set("Content-Length", strconv.Itoa(0))
|
||||
|
||||
resp, err := config.cc_client.Do(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return false, fmt.Errorf("validateJWT: unexpected status code %d when validating JWT against %q",
|
||||
resp.StatusCode, req.URL)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (config *ControllerCfg) validateLegacyUser() (bool, error) {
|
||||
/*
|
||||
Validate legacy user by obtaining a session key, which will be used for authenticating subsequent API calls
|
||||
to DECORT controller.
|
||||
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.
|
||||
*/
|
||||
if config.auth_mode_code == MODE_UNDEF {
|
||||
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)
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("username", config.legacy_user)
|
||||
params.Add("password", config.legacy_password)
|
||||
params_str := params.Encode()
|
||||
|
||||
req, err := http.NewRequest("POST", config.controller_url+"/restmachine/cloudapi/user/authenticate", strings.NewReader(params_str))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Content-Length", strconv.Itoa(len(params_str)))
|
||||
|
||||
resp, err := config.cc_client.Do(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
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 := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// validation successful - keep session ID for future use
|
||||
config.legacy_sid = string(responseData)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (config *ControllerCfg) CloudAPI() *cloudapi.CloudAPI {
|
||||
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{}
|
||||
}
|
||||
}
|
||||
func (config *ControllerCfg) CloudBroker() *cloudbroker.CloudBroker {
|
||||
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{}
|
||||
}
|
||||
}
|
||||
|
||||
func (config *ControllerCfg) SDN() *sdn.SDN {
|
||||
switch config.auth_mode_code {
|
||||
case MODE_LEGACY:
|
||||
client, _ := config.caller.(*decort.LegacyDecortClient)
|
||||
return client.SDN()
|
||||
case MODE_DECS3O:
|
||||
client, _ := config.caller.(*decort.DecortClient)
|
||||
return client.SDN()
|
||||
case MODE_BVS:
|
||||
client, _ := config.caller.(*decort.BVSDecortClient)
|
||||
return client.SDN()
|
||||
default:
|
||||
return &sdn.SDN{}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user