mirror of
https://github.com/kubescape/kubescape.git
synced 2026-04-15 06:58:11 +00:00
834 lines
21 KiB
Go
834 lines
21 KiB
Go
package getter
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
// Kubescape API endpoints
|
|
|
|
// production
|
|
ksCloudERURL = "report.armo.cloud" // API reports URL
|
|
ksCloudBEURL = "api.armosec.io" // API backend URL
|
|
ksCloudFEURL = "cloud.armosec.io" // API frontend (UI) URIL
|
|
ksCloudAUTHURL = "auth.armosec.io" // API login URL
|
|
|
|
// staging
|
|
ksCloudStageERURL = "report-ks.eustage2.cyberarmorsoft.com"
|
|
ksCloudStageBEURL = "api-stage.armosec.io"
|
|
ksCloudStageFEURL = "armoui-stage.armosec.io"
|
|
ksCloudStageAUTHURL = "eggauth-stage.armosec.io"
|
|
|
|
// dev
|
|
ksCloudDevERURL = "report.eudev3.cyberarmorsoft.com"
|
|
ksCloudDevBEURL = "api-dev.armosec.io"
|
|
ksCloudDevFEURL = "cloud-dev.armosec.io"
|
|
ksCloudDevAUTHURL = "eggauth-dev.armosec.io"
|
|
|
|
// Kubescape API routes
|
|
pathAttackTracks = "/api/v1/attackTracks"
|
|
pathFrameworks = "/api/v1/armoFrameworks"
|
|
pathExceptions = "/api/v1/armoPostureExceptions"
|
|
pathTenant = "/api/v1/tenants/createTenant"
|
|
pathExceptionPolicy = "/api/v1/postureExceptionPolicy"
|
|
pathCustomerConfig = "/api/v1/armoCustomerConfiguration"
|
|
pathLogin = "/identity/resources/auth/v1/api-token"
|
|
pathToken = "/api/v1/openid_customers" //nolint:gosec
|
|
|
|
// reports upload route
|
|
pathReport = "/k8s/v2/postureReport"
|
|
|
|
// Kubescape UI routes
|
|
pathUIScan = "/compliance/%s"
|
|
pathUIRBAC = "/rbac-visualizer"
|
|
pathUIRepository = "/repository-scanning/%s"
|
|
pathUIDashboard = "/dashboard/"
|
|
pathUISign = "/account/sign-up"
|
|
)
|
|
|
|
const (
|
|
// default dummy GUID when not defined
|
|
fallbackGUID = "11111111-1111-1111-1111-111111111111"
|
|
|
|
// URL query parameters
|
|
queryParamGUID = "customerGUID"
|
|
queryParamScope = "scope"
|
|
queryParamFrameworkName = "frameworkName"
|
|
queryParamPolicyName = "policyName"
|
|
queryParamClusterName = "clusterName"
|
|
queryParamContextName = "contextName"
|
|
|
|
queryParamUTMSource = "utm_source"
|
|
queryParamUTMMedium = "utm_medium"
|
|
// queryParamUTMCampaign = "utm_campaign"
|
|
queryParamReport = "reportGUID"
|
|
queryParamInvitationToken = "invitationToken"
|
|
|
|
authenticationCookie = "auth"
|
|
)
|
|
|
|
var (
|
|
// Errors returned by the API
|
|
|
|
ErrLoginMissingAccountID = errors.New("failed to login, missing accountID")
|
|
ErrLoginMissingClientID = errors.New("failed to login, missing clientID")
|
|
ErrLoginMissingSecretKey = errors.New("failed to login, missing secretKey")
|
|
ErrAPINotPublic = errors.New("control api is not public")
|
|
)
|
|
|
|
var (
|
|
// globalKSCloudAPIConnector is a static global instance of the KS Cloud client,
|
|
// to be initialized with SetKSCloudAPIConnector.
|
|
globalKSCloudAPIConnector *KSCloudAPI
|
|
|
|
_ IPolicyGetter = &KSCloudAPI{}
|
|
_ IExceptionsGetter = &KSCloudAPI{}
|
|
_ IAttackTracksGetter = &KSCloudAPI{}
|
|
_ IControlsInputsGetter = &KSCloudAPI{}
|
|
)
|
|
|
|
// KSCloudAPI allows to access the API of the Kubescape Cloud offering.
|
|
type KSCloudAPI struct {
|
|
authCookie *http.Cookie
|
|
*ksCloudOptions
|
|
authhost string
|
|
cloudAPIURL string
|
|
secretKey string
|
|
accountID string
|
|
cloudAuthURL string
|
|
invitationToken string
|
|
reporthost string
|
|
scheme string
|
|
host string
|
|
authscheme string
|
|
clientID string
|
|
uischeme string
|
|
uihost string
|
|
reportscheme string
|
|
feToken feLoginResponse
|
|
loggedIn bool
|
|
}
|
|
|
|
// SetKSCloudAPIConnector registers a global instance of the KS Cloud client.
|
|
//
|
|
// NOTE: cannot be used concurrently.
|
|
func SetKSCloudAPIConnector(ksCloudAPI *KSCloudAPI) {
|
|
globalKSCloudAPIConnector = ksCloudAPI
|
|
}
|
|
|
|
// GetKSCloudAPIConnector returns a shallow clone of the KS Cloud client registered for this package.
|
|
//
|
|
// NOTE: cannot be used concurrently with SetKSCloudAPIConnector.
|
|
func GetKSCloudAPIConnector() *KSCloudAPI {
|
|
if globalKSCloudAPIConnector == nil {
|
|
SetKSCloudAPIConnector(NewKSCloudAPIProd())
|
|
}
|
|
|
|
// we return a shallow clone that may be freely modified by the caller.
|
|
client := *globalKSCloudAPIConnector
|
|
options := *globalKSCloudAPIConnector.ksCloudOptions
|
|
client.ksCloudOptions = &options
|
|
|
|
return &client
|
|
}
|
|
|
|
// NewKSCloudAPIDev returns a KS Cloud client pointing to a development environment.
|
|
func NewKSCloudAPIDev(opts ...KSCloudOption) *KSCloudAPI {
|
|
devOpts := []KSCloudOption{
|
|
WithFrontendURL(ksCloudDevFEURL),
|
|
WithReportURL(ksCloudDevERURL),
|
|
}
|
|
devOpts = append(devOpts, opts...)
|
|
|
|
apiObj := newKSCloudAPI(
|
|
ksCloudDevBEURL,
|
|
ksCloudDevAUTHURL,
|
|
devOpts...,
|
|
)
|
|
|
|
return apiObj
|
|
}
|
|
|
|
// NewKSCloudAPIDProd returns a KS Cloud client pointing to a production environment.
|
|
func NewKSCloudAPIProd(opts ...KSCloudOption) *KSCloudAPI {
|
|
prodOpts := []KSCloudOption{
|
|
WithFrontendURL(ksCloudFEURL),
|
|
WithReportURL(ksCloudERURL),
|
|
}
|
|
prodOpts = append(prodOpts, opts...)
|
|
|
|
return newKSCloudAPI(
|
|
ksCloudBEURL,
|
|
ksCloudAUTHURL,
|
|
prodOpts...,
|
|
)
|
|
}
|
|
|
|
// NewKSCloudAPIStaging returns a KS Cloud client pointing to a testing environment.
|
|
func NewKSCloudAPIStaging(opts ...KSCloudOption) *KSCloudAPI {
|
|
stagingOpts := []KSCloudOption{
|
|
WithFrontendURL(ksCloudStageFEURL),
|
|
WithReportURL(ksCloudStageERURL),
|
|
}
|
|
stagingOpts = append(stagingOpts, opts...)
|
|
|
|
return newKSCloudAPI(
|
|
ksCloudStageBEURL,
|
|
ksCloudStageAUTHURL,
|
|
stagingOpts...,
|
|
)
|
|
}
|
|
|
|
// NewKSCloudAPICustomed returns a KS Cloud client with configurable API and authentication endpoints.
|
|
func NewKSCloudAPICustomized(ksCloudAPIURL, ksCloudAuthURL string, opts ...KSCloudOption) *KSCloudAPI {
|
|
return newKSCloudAPI(
|
|
ksCloudAPIURL,
|
|
ksCloudAuthURL,
|
|
opts...,
|
|
)
|
|
}
|
|
|
|
func newKSCloudAPI(apiURL, authURL string, opts ...KSCloudOption) *KSCloudAPI {
|
|
api := &KSCloudAPI{
|
|
cloudAPIURL: apiURL,
|
|
cloudAuthURL: authURL,
|
|
ksCloudOptions: ksCloudOptionsWithDefaults(opts),
|
|
}
|
|
|
|
api.SetCloudAPIURL(apiURL)
|
|
api.SetCloudAuthURL(authURL)
|
|
api.SetCloudUIURL(api.cloudUIURL)
|
|
api.SetCloudReportURL(api.cloudReportURL)
|
|
|
|
return api
|
|
}
|
|
|
|
// Get retrieves an API resource.
|
|
//
|
|
// The response is serialized as a string.
|
|
//
|
|
// The caller may specify extra headers.
|
|
//
|
|
// By default, all authentication headers are added.
|
|
func (api *KSCloudAPI) Get(fullURL string, headers map[string]string) (string, error) {
|
|
rdr, size, err := api.get(fullURL, withExtraHeaders(headers))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer rdr.Close()
|
|
|
|
return readString(rdr, size)
|
|
}
|
|
|
|
// Post creates an API resource.
|
|
//
|
|
// The response is serialized as a string.
|
|
//
|
|
// The caller may specify extra headers.
|
|
//
|
|
// By default, the body content type is set to JSON and all authentication headers are added.
|
|
func (api *KSCloudAPI) Post(fullURL string, headers map[string]string, body []byte) (string, error) {
|
|
rdr, size, err := api.post(fullURL, body, withContentJSON(true), withExtraHeaders(headers))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer rdr.Close()
|
|
|
|
return readString(rdr, size)
|
|
}
|
|
|
|
// Delete an API resource.
|
|
//
|
|
// The response is serialized as a string.
|
|
//
|
|
// The caller may specify extra headers.
|
|
//
|
|
// By default, all authentication headers are added.
|
|
func (api *KSCloudAPI) Delete(fullURL string, headers map[string]string) (string, error) {
|
|
rdr, size, err := api.delete(fullURL, withExtraHeaders(headers))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer rdr.Close()
|
|
|
|
return readString(rdr, size)
|
|
}
|
|
|
|
// GetAccountID returns the customer account's GUID.
|
|
func (api *KSCloudAPI) GetAccountID() string { return api.accountID }
|
|
|
|
// IsLoggedIn indicates if the client has sucessfully authenticated.
|
|
func (api *KSCloudAPI) IsLoggedIn() bool { return api.loggedIn }
|
|
|
|
func (api *KSCloudAPI) GetClientID() string { return api.clientID }
|
|
func (api *KSCloudAPI) GetSecretKey() string { return api.secretKey }
|
|
func (api *KSCloudAPI) GetCloudReportURL() string { return api.cloudReportURL }
|
|
func (api *KSCloudAPI) GetCloudAPIURL() string { return api.cloudAPIURL }
|
|
func (api *KSCloudAPI) GetCloudUIURL() string { return api.cloudUIURL }
|
|
func (api *KSCloudAPI) GetCloudAuthURL() string { return api.cloudAuthURL }
|
|
func (api *KSCloudAPI) GetInvitationToken() string { return api.invitationToken }
|
|
|
|
func (api *KSCloudAPI) SetAccountID(accountID string) { api.accountID = accountID }
|
|
func (api *KSCloudAPI) SetClientID(clientID string) { api.clientID = clientID }
|
|
func (api *KSCloudAPI) SetSecretKey(secretKey string) { api.secretKey = secretKey }
|
|
func (api *KSCloudAPI) SetInvitationToken(token string) { api.invitationToken = token }
|
|
|
|
func (api *KSCloudAPI) SetCloudAPIURL(cloudAPIURL string) {
|
|
api.cloudAPIURL = cloudAPIURL
|
|
api.scheme, api.host = parseHost(cloudAPIURL)
|
|
}
|
|
|
|
func (api *KSCloudAPI) SetCloudUIURL(cloudUIURL string) {
|
|
api.cloudUIURL = cloudUIURL
|
|
api.uischeme, api.uihost = parseHost(cloudUIURL)
|
|
}
|
|
|
|
func (api *KSCloudAPI) SetCloudAuthURL(cloudAuthURL string) {
|
|
api.cloudAuthURL = cloudAuthURL
|
|
api.authscheme, api.authhost = parseHost(cloudAuthURL)
|
|
}
|
|
|
|
func (api *KSCloudAPI) SetCloudReportURL(cloudReportURL string) {
|
|
api.cloudReportURL = cloudReportURL
|
|
api.reportscheme, api.reporthost = parseHost(cloudReportURL)
|
|
}
|
|
|
|
func (api *KSCloudAPI) GetAttackTracks() ([]AttackTrack, error) {
|
|
rdr, _, err := api.get(api.getAttackTracksURL())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rdr.Close()
|
|
|
|
attackTracks, err := decode[[]AttackTrack](rdr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return attackTracks, nil
|
|
}
|
|
|
|
func (api *KSCloudAPI) getAttackTracksURL() string {
|
|
return api.buildAPIURL(
|
|
pathAttackTracks,
|
|
api.paramsWithGUID()...,
|
|
)
|
|
}
|
|
|
|
// GetFramework retrieves a framework by name.
|
|
func (api *KSCloudAPI) GetFramework(frameworkName string) (*Framework, error) {
|
|
rdr, _, err := api.get(api.getFrameworkURL(frameworkName))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rdr.Close()
|
|
|
|
framework, err := decode[Framework](rdr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &framework, err
|
|
}
|
|
|
|
func (api *KSCloudAPI) getFrameworkURL(frameworkName string) string {
|
|
if isNativeFramework(frameworkName) {
|
|
// Native framework name is normalized as upper case, but for a custom framework the name remains unaltered
|
|
frameworkName = strings.ToUpper(frameworkName)
|
|
}
|
|
|
|
return api.buildAPIURL(
|
|
pathFrameworks,
|
|
append(
|
|
api.paramsWithGUID(),
|
|
queryParamFrameworkName, frameworkName,
|
|
)...,
|
|
)
|
|
}
|
|
|
|
// GetFrameworks returns all registered frameworks.
|
|
func (api *KSCloudAPI) GetFrameworks() ([]Framework, error) {
|
|
rdr, _, err := api.get(api.getListFrameworkURL())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rdr.Close()
|
|
|
|
frameworks, err := decode[[]Framework](rdr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return frameworks, err
|
|
}
|
|
|
|
func (api *KSCloudAPI) getListFrameworkURL() string {
|
|
return api.buildAPIURL(
|
|
pathFrameworks,
|
|
api.paramsWithGUID()...,
|
|
)
|
|
}
|
|
|
|
// ListCustomFrameworks lists the names of all non-native frameworks that have been registered for this account.
|
|
func (api *KSCloudAPI) ListCustomFrameworks() ([]string, error) {
|
|
frameworks, err := api.GetFrameworks()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
frameworkList := make([]string, 0, len(frameworks))
|
|
for _, framework := range frameworks {
|
|
if isNativeFramework(framework.Name) {
|
|
continue
|
|
}
|
|
|
|
frameworkList = append(frameworkList, framework.Name)
|
|
}
|
|
|
|
return frameworkList, nil
|
|
}
|
|
|
|
// ListFrameworks list the names of all registered frameworks.
|
|
func (api *KSCloudAPI) ListFrameworks() ([]string, error) {
|
|
frameworks, err := api.GetFrameworks()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
frameworkList := make([]string, 0, len(frameworks))
|
|
for _, framework := range frameworks {
|
|
name := framework.Name
|
|
if isNativeFramework(framework.Name) {
|
|
name = strings.ToLower(framework.Name)
|
|
}
|
|
|
|
frameworkList = append(frameworkList, name)
|
|
}
|
|
|
|
return frameworkList, nil
|
|
}
|
|
|
|
// GetExceptions returns exception policies.
|
|
func (api *KSCloudAPI) GetExceptions(clusterName string) ([]PostureExceptionPolicy, error) {
|
|
rdr, _, err := api.get(api.getExceptionsURL(clusterName))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rdr.Close()
|
|
|
|
exceptions, err := decode[[]PostureExceptionPolicy](rdr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return exceptions, nil
|
|
}
|
|
|
|
func (api *KSCloudAPI) getExceptionsURL(clusterName string) string {
|
|
return api.buildAPIURL(
|
|
pathExceptions,
|
|
api.paramsWithGUID()...,
|
|
)
|
|
// queryParamClusterName, clusterName, // TODO - fix customer name support in Armo BE
|
|
}
|
|
|
|
// GetTenant retrieves the credentials for the calling tenant.
|
|
//
|
|
// The tenant ID overides any already provided account ID.
|
|
func (api *KSCloudAPI) GetTenant() (*TenantResponse, error) {
|
|
rdr, _, err := api.get(api.getTenantURL())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rdr.Close()
|
|
|
|
tenant, err := decode[TenantResponse](rdr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if tenant.TenantID != "" {
|
|
api.accountID = tenant.TenantID
|
|
}
|
|
|
|
return &tenant, nil
|
|
}
|
|
|
|
func (api *KSCloudAPI) getTenantURL() string {
|
|
var params []string
|
|
if api.accountID != "" {
|
|
params = []string{
|
|
queryParamGUID, api.accountID, // NOTE: no fallback in this case
|
|
}
|
|
}
|
|
|
|
return api.buildAPIURL(
|
|
pathTenant,
|
|
params...,
|
|
)
|
|
}
|
|
|
|
// GetAccountConfig yields the account configuration.
|
|
func (api *KSCloudAPI) GetAccountConfig(clusterName string) (*CustomerConfig, error) {
|
|
if api.accountID == "" {
|
|
return &CustomerConfig{}, nil
|
|
}
|
|
|
|
rdr, _, err := api.get(api.getAccountConfig(clusterName))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rdr.Close()
|
|
|
|
accountConfig, err := decode[CustomerConfig](rdr)
|
|
if err != nil {
|
|
// retry with default scope
|
|
rdr, _, err = api.get(api.getAccountConfigDefault(clusterName))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rdr.Close()
|
|
|
|
accountConfig, err = decode[CustomerConfig](rdr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &accountConfig, nil
|
|
}
|
|
|
|
func (api *KSCloudAPI) getAccountConfig(clusterName string) string {
|
|
params := api.paramsWithGUID()
|
|
|
|
if clusterName != "" { // TODO - fix customer name support in Armo BE
|
|
params = append(params, queryParamClusterName, clusterName)
|
|
}
|
|
|
|
return api.buildAPIURL(
|
|
pathCustomerConfig,
|
|
params...,
|
|
)
|
|
}
|
|
|
|
func (api *KSCloudAPI) getAccountConfigDefault(clusterName string) string {
|
|
params := append(
|
|
api.paramsWithGUID(),
|
|
queryParamScope, "customer",
|
|
)
|
|
|
|
if clusterName != "" { // TODO - fix customer name support in Armo BE
|
|
params = append(params, queryParamClusterName, clusterName)
|
|
}
|
|
|
|
return api.buildAPIURL(
|
|
pathCustomerConfig,
|
|
params...,
|
|
)
|
|
}
|
|
|
|
// GetControlsInputs returns the controls inputs configured in the account configuration.
|
|
func (api *KSCloudAPI) GetControlsInputs(clusterName string) (map[string][]string, error) {
|
|
accountConfig, err := api.GetAccountConfig(clusterName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return accountConfig.Settings.PostureControlInputs, nil
|
|
}
|
|
|
|
// GetControl is currently not exposed as a public API endpoint.
|
|
func (api *KSCloudAPI) GetControl(ID string) (*Control, error) {
|
|
return nil, ErrAPINotPublic
|
|
}
|
|
|
|
// ListControls is currently not exposed as a public API endpoint.
|
|
func (api *KSCloudAPI) ListControls() ([]string, error) {
|
|
return nil, ErrAPINotPublic
|
|
}
|
|
|
|
// PostExceptions registers a list of exceptions.
|
|
func (api *KSCloudAPI) PostExceptions(exceptions []PostureExceptionPolicy) error {
|
|
target := api.exceptionsURL("")
|
|
|
|
for i := range exceptions {
|
|
jazon, err := json.Marshal(exceptions[i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, _, err = api.post(target, jazon, withContentJSON(true))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Delete exception removes a registered exception rule.
|
|
func (api *KSCloudAPI) DeleteException(exceptionName string) error {
|
|
_, _, err := api.delete(api.exceptionsURL(exceptionName))
|
|
|
|
return err
|
|
}
|
|
|
|
func (api *KSCloudAPI) exceptionsURL(exceptionsPolicyName string) string {
|
|
params := api.paramsWithGUID()
|
|
if exceptionsPolicyName != "" { // for delete
|
|
params = append(params, queryParamPolicyName, exceptionsPolicyName)
|
|
}
|
|
|
|
return api.buildAPIURL(
|
|
pathExceptionPolicy,
|
|
params...,
|
|
)
|
|
}
|
|
|
|
// SubmitReport uploads a posture report.
|
|
func (api *KSCloudAPI) SubmitReport(report *PostureReport) error {
|
|
jazon, err := json.Marshal(report)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, _, err = api.post(api.postReportURL(report.ClusterName, report.ReportID), jazon, withContentJSON(true), withToken(api.invitationToken))
|
|
|
|
return err
|
|
}
|
|
|
|
func (api *KSCloudAPI) postReportURL(cluster, reportID string) string {
|
|
return api.buildReportURL(pathReport,
|
|
append(
|
|
api.paramsWithGUID(),
|
|
queryParamContextName, cluster,
|
|
queryParamClusterName, cluster, // deprecated
|
|
queryParamReport, reportID,
|
|
)...,
|
|
)
|
|
}
|
|
|
|
// ViewReportURL yields the frontend URL to view a posture report (e.g. from a repository scan).
|
|
func (api *KSCloudAPI) ViewReportURL(reportID string) string {
|
|
return api.buildUIURL(
|
|
fmt.Sprintf(pathUIRepository, reportID),
|
|
)
|
|
}
|
|
|
|
// ViewDashboardURL yields the frontend URL for the dashboard.
|
|
func (api *KSCloudAPI) ViewDashboardURL() string {
|
|
return api.buildUIURL(
|
|
pathUIDashboard,
|
|
)
|
|
}
|
|
|
|
// ViewRBACURL yields the frontend URL to visualize RBAC.
|
|
func (api *KSCloudAPI) ViewRBACURL() string {
|
|
return api.buildUIURL(
|
|
pathUIRBAC,
|
|
)
|
|
}
|
|
|
|
// ViewRBACURL yields the frontend URL to check the compliance of a scanned cluster.
|
|
func (api *KSCloudAPI) ViewScanURL(cluster string) string {
|
|
return api.buildUIURL(
|
|
fmt.Sprintf(pathUIScan, cluster),
|
|
)
|
|
}
|
|
|
|
// ViewSignURL yields the frontend login page.
|
|
func (api *KSCloudAPI) ViewSignURL() string {
|
|
params := api.paramsWithGUID()
|
|
params = append(params, api.paramsWithUTM()...)
|
|
params = append(params, queryParamInvitationToken, api.invitationToken)
|
|
|
|
return api.buildUIURL(
|
|
pathUISign,
|
|
params...,
|
|
)
|
|
}
|
|
|
|
// Login to the KS Cloud using the caller's accountID, clientID and secret key.
|
|
func (api *KSCloudAPI) Login() error {
|
|
if err := api.loginRequirements(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 1. acquire auth token
|
|
body, err := json.Marshal(feLoginData{ClientId: api.clientID, Secret: api.secretKey})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rdr, _, err := api.post(api.authTokenURL(), body, withContentJSON(true))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rdr.Close()
|
|
|
|
resp, err := decode[feLoginResponse](rdr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
api.feToken = resp
|
|
|
|
// 2. acquire auth cookie
|
|
// Now that we have the JWT token, acquire a cookie from the API
|
|
api.authCookie, err = api.getAuthCookie()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
api.loggedIn = true
|
|
|
|
return nil
|
|
}
|
|
|
|
func (api *KSCloudAPI) authTokenURL() string {
|
|
return api.buildAuthURL(pathLogin)
|
|
}
|
|
|
|
func (api *KSCloudAPI) getOpenidURL() string {
|
|
return api.buildAPIURL(pathToken)
|
|
}
|
|
|
|
func (api *KSCloudAPI) getAuthCookie() (*http.Cookie, error) {
|
|
selectCustomer := ksCloudSelectCustomer{SelectedCustomerGuid: api.accountID}
|
|
body, err := json.Marshal(selectCustomer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
target := api.getOpenidURL()
|
|
o := api.defaultRequestOptions([]requestOption{withContentJSON(true), withCookie(nil)})
|
|
req, err := http.NewRequestWithContext(o.reqContext, http.MethodPost, target, bytes.NewBuffer(body))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
o.setHeaders(req)
|
|
o.traceReq(req)
|
|
resp, err := api.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
o.traceResp(resp)
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("failed to get cookie from %s: status %d", target, resp.StatusCode)
|
|
}
|
|
|
|
for _, cookie := range resp.Cookies() {
|
|
if cookie.Name == authenticationCookie {
|
|
return cookie, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("no auth cookie in response from %s", target)
|
|
}
|
|
|
|
func (api *KSCloudAPI) loginRequirements() error {
|
|
if api.accountID == "" {
|
|
return ErrLoginMissingAccountID
|
|
}
|
|
|
|
if api.clientID == "" {
|
|
return ErrLoginMissingClientID
|
|
}
|
|
|
|
if api.secretKey == "" {
|
|
return ErrLoginMissingSecretKey
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// defaultRequestOptions adds standard authentication headers to all requests
|
|
func (api *KSCloudAPI) defaultRequestOptions(opts []requestOption) *requestOptions {
|
|
optionsWithDefaults := append(make([]requestOption, 0, 4),
|
|
withToken(api.feToken.Token),
|
|
withCookie(api.authCookie),
|
|
withTrace(api.withTrace),
|
|
)
|
|
optionsWithDefaults = append(optionsWithDefaults, opts...)
|
|
|
|
return requestOptionsWithDefaults(optionsWithDefaults)
|
|
}
|
|
|
|
func (api *KSCloudAPI) get(fullURL string, opts ...requestOption) (io.ReadCloser, int64, error) {
|
|
o := api.defaultRequestOptions(opts)
|
|
req, err := http.NewRequestWithContext(o.reqContext, http.MethodGet, fullURL, nil)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return api.do(req, o)
|
|
}
|
|
|
|
func (api *KSCloudAPI) post(fullURL string, body []byte, opts ...requestOption) (io.ReadCloser, int64, error) {
|
|
o := api.defaultRequestOptions(opts)
|
|
req, err := http.NewRequestWithContext(o.reqContext, http.MethodPost, fullURL, bytes.NewBuffer(body))
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return api.do(req, o)
|
|
}
|
|
|
|
func (api *KSCloudAPI) delete(fullURL string, opts ...requestOption) (io.ReadCloser, int64, error) {
|
|
o := api.defaultRequestOptions(opts)
|
|
req, err := http.NewRequestWithContext(o.reqContext, http.MethodDelete, fullURL, nil)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return api.do(req, o)
|
|
}
|
|
|
|
func (api *KSCloudAPI) do(req *http.Request, o *requestOptions) (io.ReadCloser, int64, error) {
|
|
o.setHeaders(req)
|
|
o.traceReq(req)
|
|
|
|
resp, err := api.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
o.traceResp(resp)
|
|
|
|
if resp.StatusCode >= 400 {
|
|
if req.URL.Path == pathLogin {
|
|
return nil, 0, errAuth(resp)
|
|
|
|
}
|
|
return nil, 0, errAPI(resp)
|
|
}
|
|
|
|
return resp.Body, resp.ContentLength, err
|
|
}
|
|
|
|
func (api *KSCloudAPI) paramsWithGUID() []string {
|
|
return append(make([]string, 0, 6),
|
|
queryParamGUID, api.getCustomerGUIDFallBack(),
|
|
)
|
|
}
|
|
|
|
func (api *KSCloudAPI) paramsWithUTM() []string {
|
|
return append(make([]string, 0, 6),
|
|
queryParamUTMSource, "ARMOgithub",
|
|
queryParamUTMMedium, "createaccount",
|
|
)
|
|
}
|
|
|
|
func (api *KSCloudAPI) getCustomerGUIDFallBack() string {
|
|
if api.accountID != "" {
|
|
return api.accountID
|
|
}
|
|
return fallbackGUID
|
|
}
|