Unverified Commit 69fbabe6 authored by Michal Jura's avatar Michal Jura
Browse files

Refactor AKS UI proxy calls

AKS Kontainer Engine v2 will use cloud credentials. For checking AKS
cluster capabilities cloud credentials should be also used.
AKS Kontainer Engine v1 should works as before.

Example test HTTP call for testing cloud credentials

    ```
    $ curl -u $TOKEN "https://localhost:8443/meta/aksVersions?cloudCredentialId=cattle-global-data:cc-abcde&tenantId=3572c671-f13a-4d01-aa5e-b100b54111fa&region=eastus
    ```
Showing with 442 additions and 437 deletions
+442 -437
......@@ -94,7 +94,7 @@ require (
github.com/prometheus/client_golang v1.9.0
github.com/prometheus/client_model v0.2.0
github.com/prometheus/common v0.15.0
github.com/rancher/aks-operator v1.0.1-rc4
github.com/rancher/aks-operator v1.0.1-rc6
github.com/rancher/apiserver v0.0.0-20210519053359-f943376c4b42
github.com/rancher/channelserver v0.5.1-0.20210421200213-5495c5f6e430
github.com/rancher/dynamiclistener v0.2.1-0.20201110045217-9b1b7d3132e8
......
package aks
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/gorilla/mux"
"github.com/rancher/norman/api/access"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
client "github.com/rancher/rancher/pkg/client/generated/management/v3"
v1 "github.com/rancher/rancher/pkg/generated/norman/core/v1"
v3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3"
"github.com/rancher/rancher/pkg/namespace"
"github.com/rancher/rancher/pkg/ref"
mgmtSchema "github.com/rancher/rancher/pkg/schemas/management.cattle.io/v3"
schema "github.com/rancher/rancher/pkg/schemas/management.cattle.io/v3"
"github.com/rancher/rancher/pkg/types/config"
"github.com/sirupsen/logrus"
)
type Capabilities struct {
AuthBaseURL string `json:"authBaseUrl"`
BaseURL string `json:"baseUrl"`
TenantID string `json:"tenantId"`
SubscriptionID string `json:"subscriptionId"`
ClientID string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
ResourceLocation string `json:"region"`
}
// AKS handler lists available resources in Azure API
type handler struct {
schemas *types.Schemas
secretsLister v1.SecretLister
clusterLister v3.ClusterLister
ac types.AccessControl
}
func NewAKSHandler(scaledContext *config.ScaledContext) http.Handler {
return &handler{
schemas: scaledContext.Schemas,
secretsLister: scaledContext.Core.Secrets(namespace.GlobalNamespace).Controller().Lister(),
clusterLister: scaledContext.Management.Clusters("").Controller().Lister(),
ac: scaledContext.AccessControl,
}
}
func (h *handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
writer.Header().Set("Content-Type", "application/json")
capa := &Capabilities{}
if credID := req.URL.Query().Get("cloudCredentialId"); credID != "" {
if errCode, err := h.getCloudCredential(req, capa, credID); err != nil {
handleErr(writer, errCode, err)
return
}
} else if req.Method == http.MethodPost {
if errCode, err := h.getCredentialsFromBody(req, capa); err != nil {
handleErr(writer, errCode, err)
return
}
} else {
handleErr(writer, http.StatusBadRequest, fmt.Errorf("cannot access Azure API without credentials to authenticate"))
return
}
var serialized []byte
var errCode int
var err error
resourceType := mux.Vars(req)["resource"]
switch resourceType {
case "aksVersions":
if serialized, errCode, err = listKubernetesVersions(req.Context(), capa); err != nil {
logrus.Debugf("[aks-handler] error getting kubernetes versions: %v", err)
handleErr(writer, errCode, err)
return
}
writer.Write(serialized)
case "aksVirtualNetworks":
if serialized, errCode, err = listVirtualNetworks(req.Context(), capa); err != nil {
logrus.Debugf("[aks-handler] error getting networks: %v", err)
handleErr(writer, errCode, err)
return
}
writer.Write(serialized)
default:
handleErr(writer, httperror.NotFound.Status, fmt.Errorf("invalid endpoint %v", resourceType))
}
}
func (h *handler) getCloudCredential(req *http.Request, cap *Capabilities, credID string) (int, error) {
ns, name := ref.Parse(credID)
if ns == "" || name == "" {
logrus.Debugf("[AKS] invalid cloud credential ID %s", credID)
return http.StatusBadRequest, fmt.Errorf("invalid cloud credential ID %s", credID)
}
var accessCred client.CloudCredential //var to check access
if err := access.ByID(h.generateAPIContext(req), &schema.Version, client.CloudCredentialType, credID, &accessCred); err != nil {
apiError, ok := err.(*httperror.APIError)
if !ok {
return httperror.NotFound.Status, err
}
if apiError.Code.Status == httperror.NotFound.Status {
return httperror.InvalidBodyContent.Status, fmt.Errorf("cloud credential not found")
}
if apiError.Code.Status != httperror.PermissionDenied.Status {
return httperror.InvalidBodyContent.Status, err
}
var clusterID string
if clusterID = req.URL.Query().Get("clusterID"); clusterID == "" {
return httperror.InvalidBodyContent.Status, fmt.Errorf("cloud credential not found")
}
if errCode, err := h.clusterCheck(h.generateAPIContext(req), clusterID, credID); err != nil {
return errCode, err
}
}
cc, err := h.secretsLister.Get(ns, name)
if err != nil {
logrus.Debugf("[AKS] error accessing cloud credential %s", credID)
return httperror.InvalidBodyContent.Status, fmt.Errorf("error accessing cloud credential %s", credID)
}
cap.TenantID = string(cc.Data["azurecredentialConfig-tenantId"])
cap.SubscriptionID = string(cc.Data["azurecredentialConfig-subscriptionId"])
cap.ClientID = string(cc.Data["azurecredentialConfig-clientId"])
cap.ClientSecret = string(cc.Data["azurecredentialConfig-clientSecret"])
cap.BaseURL = req.URL.Query().Get("baseUrl")
if cap.BaseURL == "" {
cap.BaseURL = azure.PublicCloud.ResourceManagerEndpoint
}
cap.AuthBaseURL = req.URL.Query().Get("authBaseUrl")
if cap.AuthBaseURL == "" {
cap.AuthBaseURL = azure.PublicCloud.ActiveDirectoryEndpoint
}
region := req.URL.Query().Get("region")
if region != "" {
cap.ResourceLocation = region
}
return http.StatusOK, nil
}
func (h *handler) clusterCheck(apiContext *types.APIContext, clusterID, cloudCredentialID string) (int, error) {
clusterInfo := map[string]interface{}{
"id": clusterID,
}
clusterSchema := h.schemas.Schema(&mgmtSchema.Version, client.ClusterType)
if err := h.ac.CanDo(v3.ClusterGroupVersionKind.Group, v3.ClusterResource.Name, "update", apiContext, clusterInfo, clusterSchema); err != nil {
return httperror.InvalidBodyContent.Status, fmt.Errorf("cluster not found")
}
cluster, err := h.clusterLister.Get("", clusterID)
if err != nil {
if httperror.IsNotFound(err) {
return httperror.InvalidBodyContent.Status, fmt.Errorf("cluster not found")
}
return httperror.ServerError.Status, err
}
if cluster.Spec.AKSConfig.AzureCredentialSecret != cloudCredentialID {
return httperror.InvalidBodyContent.Status, fmt.Errorf("cloud credential not found")
}
return http.StatusOK, nil
}
func (h *handler) getCredentialsFromBody(req *http.Request, cap *Capabilities) (int, error) {
raw, err := ioutil.ReadAll(req.Body)
if err != nil {
return http.StatusBadRequest, fmt.Errorf("cannot read request body: %v", err)
}
if err = json.Unmarshal(raw, &cap); err != nil {
return http.StatusBadRequest, fmt.Errorf("cannot parse request body: %v", err)
}
if cap.SubscriptionID == "" {
return http.StatusBadRequest, fmt.Errorf("invalid subscriptionId")
}
if cap.TenantID == "" {
return http.StatusBadRequest, fmt.Errorf("invalid tenantId")
}
if cap.ClientID == "" {
return http.StatusBadRequest, fmt.Errorf("invalid clientId")
}
if cap.ClientSecret == "" {
return http.StatusBadRequest, fmt.Errorf("invalid clientSecret")
}
if cap.BaseURL == "" {
cap.BaseURL = azure.PublicCloud.ResourceManagerEndpoint
}
if cap.AuthBaseURL == "" {
cap.AuthBaseURL = azure.PublicCloud.ActiveDirectoryEndpoint
}
return http.StatusOK, nil
}
func (h *handler) generateAPIContext(req *http.Request) *types.APIContext {
return &types.APIContext{
Method: req.Method,
Request: req,
Schemas: h.schemas,
Query: map[string][]string{},
}
}
func handleErr(writer http.ResponseWriter, errorCode int, originalErr error) {
asJSON := []byte(fmt.Sprintf(`{"error":"%v"}`, originalErr))
writer.WriteHeader(errorCode)
writer.Write(asJSON)
}
package aks
import (
"context"
"encoding/json"
"fmt"
"net/http"
"regexp"
"sort"
"github.com/Azure/azure-sdk-for-go/services/containerservice/mgmt/2020-09-01/containerservice"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-07-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/mcuadros/go-version"
)
type virtualNetworksResponseBody struct {
Name string `json:"name"`
ResourceGroup string `json:"resourceGroup"`
Subnets []subnet `json:"subnets"`
}
type subnet struct {
Name string `json:"name"`
AddressRange string `json:"addressRange"`
}
var matchAzureNetwork = regexp.MustCompile("/resourceGroups/(.+?)/")
func NewClientAuthorizer(cap *Capabilities) (autorest.Authorizer, error) {
oauthConfig, err := adal.NewOAuthConfig(cap.AuthBaseURL, cap.TenantID)
if err != nil {
return nil, err
}
spToken, err := adal.NewServicePrincipalToken(*oauthConfig, cap.ClientID, cap.ClientSecret, cap.BaseURL)
if err != nil {
return nil, fmt.Errorf("couldn't authenticate to Azure cloud with error: %v", err)
}
return autorest.NewBearerAuthorizer(spToken), nil
}
func NewContainerServiceClient(cap *Capabilities) (*containerservice.ContainerServicesClient, error) {
authorizer, err := NewClientAuthorizer(cap)
if err != nil {
return nil, err
}
containerService := containerservice.NewContainerServicesClientWithBaseURI(cap.BaseURL, cap.SubscriptionID)
containerService.Authorizer = authorizer
return &containerService, nil
}
func NewNetworkServiceClient(cap *Capabilities) (*network.VirtualNetworksClient, error) {
authorizer, err := NewClientAuthorizer(cap)
if err != nil {
return nil, err
}
containerService := network.NewVirtualNetworksClientWithBaseURI(cap.BaseURL, cap.SubscriptionID)
containerService.Authorizer = authorizer
return &containerService, nil
}
type sortableVersion []string
func (s sortableVersion) Len() int {
return len(s)
}
func (s sortableVersion) Swap(a, b int) {
s[a], s[b] = s[b], s[a]
}
func (s sortableVersion) Less(a, b int) bool {
return version.Compare(s[a], s[b], "<")
}
func listKubernetesVersions(ctx context.Context, cap *Capabilities) ([]byte, int, error) {
if cap.ResourceLocation == "" {
return nil, http.StatusBadRequest, fmt.Errorf("region is required")
}
clientContainer, err := NewContainerServiceClient(cap)
if err != nil {
return nil, http.StatusInternalServerError, err
}
orchestrators, err := clientContainer.ListOrchestrators(ctx, cap.ResourceLocation, "managedClusters")
if err != nil {
return nil, http.StatusBadRequest, fmt.Errorf("failed to get orchestrators: %v", err)
}
if orchestrators.Orchestrators == nil {
return nil, http.StatusBadRequest, fmt.Errorf("no version profiles returned: %v", err)
}
var kubernetesVersions []string
for _, profile := range *orchestrators.Orchestrators {
if profile.OrchestratorType == nil || profile.OrchestratorVersion == nil {
return nil, http.StatusInternalServerError, fmt.Errorf("unexpected nil orchestrator type or version")
}
if *profile.OrchestratorType == "Kubernetes" {
kubernetesVersions = append(kubernetesVersions, *profile.OrchestratorVersion)
}
}
sort.Sort(sortableVersion(kubernetesVersions))
return encodeOutput(kubernetesVersions)
}
func listVirtualNetworks(ctx context.Context, cap *Capabilities) ([]byte, int, error) {
clientNetwork, err := NewNetworkServiceClient(cap)
if err != nil {
return nil, http.StatusInternalServerError, err
}
result, err := clientNetwork.ListAll(ctx)
if err != nil {
return nil, http.StatusBadRequest, fmt.Errorf("failed to get networks: %v", err)
}
var networks []virtualNetworksResponseBody
for result.NotDone() {
var batch []virtualNetworksResponseBody
for _, azureNetwork := range result.Values() {
var subnets []subnet
if azureNetwork.Subnets != nil {
for _, azureSubnet := range *azureNetwork.Subnets {
if azureSubnet.Name != nil {
subnets = append(subnets, subnet{
Name: *azureSubnet.Name,
AddressRange: *azureSubnet.AddressPrefix,
})
}
}
}
if azureNetwork.ID == nil {
return nil, http.StatusInternalServerError, fmt.Errorf("no ID on virtual network")
}
match := matchAzureNetwork.FindStringSubmatch(*azureNetwork.ID)
if len(match) < 2 || match[1] == "" {
return nil, http.StatusInternalServerError, fmt.Errorf("could not parse virtual network ID")
}
if azureNetwork.Name == nil {
return nil, http.StatusInternalServerError, fmt.Errorf("no name on virtual network")
}
batch = append(batch, virtualNetworksResponseBody{
Name: *azureNetwork.Name,
ResourceGroup: match[1],
Subnets: subnets,
})
}
networks = append(networks, batch...)
err = result.NextWithContext(ctx)
if err != nil {
return nil, http.StatusInternalServerError, err
}
}
return encodeOutput(networks)
}
func encodeOutput(result interface{}) ([]byte, int, error) {
data, err := json.Marshal(&result)
if err != nil {
return data, http.StatusInternalServerError, err
}
return data, http.StatusOK, err
}
......@@ -6,7 +6,7 @@ replace k8s.io/client-go => k8s.io/client-go v0.21.0
require (
github.com/pkg/errors v0.9.1
github.com/rancher/aks-operator v1.0.1-rc4
github.com/rancher/aks-operator v1.0.1-rc6
github.com/rancher/eks-operator v1.0.6-rc1
github.com/rancher/fleet/pkg/apis v0.0.0-20210428191153-f414eab0e4de
github.com/rancher/gke-operator v1.0.1
......
......@@ -656,8 +656,8 @@ github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULU
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA=
github.com/rancher/aks-operator v1.0.1-rc4 h1:H/GCiEFetQt1/dRcee1y8vx0eSJCsCuiV/rTePw0ADM=
github.com/rancher/aks-operator v1.0.1-rc4/go.mod h1:c3rHKpVkpWhPCgcMQue5BfDAhrSUrvXK33JHFKgdU+w=
github.com/rancher/aks-operator v1.0.1-rc6 h1:ML5nh0509YiTKESJknXa8FsDrtKQThvFbZ/jWUFxdUU=
github.com/rancher/aks-operator v1.0.1-rc6/go.mod h1:c3rHKpVkpWhPCgcMQue5BfDAhrSUrvXK33JHFKgdU+w=
github.com/rancher/eks-operator v1.0.6-rc1 h1:VY2lLcaHtBMByd7pq7px4fB+GW0cd0cfessODbmH+Tc=
github.com/rancher/eks-operator v1.0.6-rc1/go.mod h1:GB2kyPtIN0r+DGlUcDgEUMsB4qRwZH7OKsraemZT+GA=
github.com/rancher/fleet/pkg/apis v0.0.0-20210428191153-f414eab0e4de h1:k8019s2eb27ACgl8qoAJjZW9i5+0437bcDwNtoBMszo=
......
......@@ -25,7 +25,6 @@ const (
AKSClusterConfigSpecFieldResourceLocation = "resourceLocation"
AKSClusterConfigSpecFieldSubnet = "subnet"
AKSClusterConfigSpecFieldTags = "tags"
AKSClusterConfigSpecFieldTenantID = "tenantId"
AKSClusterConfigSpecFieldVirtualNetwork = "virtualNetwork"
AKSClusterConfigSpecFieldVirtualNetworkResourceGroup = "virtualNetworkResourceGroup"
AKSClusterConfigSpecFieldWindowsAdminPassword = "windowsAdminPassword"
......@@ -33,32 +32,31 @@ const (
)
type AKSClusterConfigSpec struct {
AuthBaseURL string `json:"authBaseUrl,omitempty" yaml:"authBaseUrl,omitempty"`
AuthBaseURL *string `json:"authBaseUrl,omitempty" yaml:"authBaseUrl,omitempty"`
AuthorizedIPRanges []string `json:"authorizedIpRanges,omitempty" yaml:"authorizedIpRanges,omitempty"`
AzureCredentialSecret string `json:"azureCredentialSecret,omitempty" yaml:"azureCredentialSecret,omitempty"`
BaseURL string `json:"baseUrl,omitempty" yaml:"baseUrl,omitempty"`
BaseURL *string `json:"baseUrl,omitempty" yaml:"baseUrl,omitempty"`
ClusterName string `json:"clusterName,omitempty" yaml:"clusterName,omitempty"`
DNSPrefix string `json:"dnsPrefix,omitempty" yaml:"dnsPrefix,omitempty"`
DNSPrefix *string `json:"dnsPrefix,omitempty" yaml:"dnsPrefix,omitempty"`
Imported bool `json:"imported,omitempty" yaml:"imported,omitempty"`
KubernetesVersion string `json:"kubernetesVersion,omitempty" yaml:"kubernetesVersion,omitempty"`
LinuxAdminUsername string `json:"linuxAdminUsername,omitempty" yaml:"linuxAdminUsername,omitempty"`
LinuxSSHPublicKey string `json:"sshPublicKey,omitempty" yaml:"sshPublicKey,omitempty"`
LoadBalancerSKU string `json:"loadBalancerSku,omitempty" yaml:"loadBalancerSku,omitempty"`
NetworkDNSServiceIP string `json:"dnsServiceIp,omitempty" yaml:"dnsServiceIp,omitempty"`
NetworkDockerBridgeCIDR string `json:"dockerBridgeCidr,omitempty" yaml:"dockerBridgeCidr,omitempty"`
NetworkPlugin string `json:"networkPlugin,omitempty" yaml:"networkPlugin,omitempty"`
NetworkPodCIDR string `json:"podCidr,omitempty" yaml:"podCidr,omitempty"`
NetworkPolicy string `json:"networkPolicy,omitempty" yaml:"networkPolicy,omitempty"`
NetworkServiceCIDR string `json:"serviceCidr,omitempty" yaml:"serviceCidr,omitempty"`
KubernetesVersion *string `json:"kubernetesVersion,omitempty" yaml:"kubernetesVersion,omitempty"`
LinuxAdminUsername *string `json:"linuxAdminUsername,omitempty" yaml:"linuxAdminUsername,omitempty"`
LinuxSSHPublicKey *string `json:"sshPublicKey,omitempty" yaml:"sshPublicKey,omitempty"`
LoadBalancerSKU *string `json:"loadBalancerSku,omitempty" yaml:"loadBalancerSku,omitempty"`
NetworkDNSServiceIP *string `json:"dnsServiceIp,omitempty" yaml:"dnsServiceIp,omitempty"`
NetworkDockerBridgeCIDR *string `json:"dockerBridgeCidr,omitempty" yaml:"dockerBridgeCidr,omitempty"`
NetworkPlugin *string `json:"networkPlugin,omitempty" yaml:"networkPlugin,omitempty"`
NetworkPodCIDR *string `json:"podCidr,omitempty" yaml:"podCidr,omitempty"`
NetworkPolicy *string `json:"networkPolicy,omitempty" yaml:"networkPolicy,omitempty"`
NetworkServiceCIDR *string `json:"serviceCidr,omitempty" yaml:"serviceCidr,omitempty"`
NodePools []AKSNodePool `json:"nodePools,omitempty" yaml:"nodePools,omitempty"`
PrivateCluster *bool `json:"privateCluster,omitempty" yaml:"privateCluster,omitempty"`
ResourceGroup string `json:"resourceGroup,omitempty" yaml:"resourceGroup,omitempty"`
ResourceLocation string `json:"resourceLocation,omitempty" yaml:"resourceLocation,omitempty"`
Subnet string `json:"subnet,omitempty" yaml:"subnet,omitempty"`
Subnet *string `json:"subnet,omitempty" yaml:"subnet,omitempty"`
Tags map[string]string `json:"tags,omitempty" yaml:"tags,omitempty"`
TenantID string `json:"tenantId,omitempty" yaml:"tenantId,omitempty"`
VirtualNetwork string `json:"virtualNetwork,omitempty" yaml:"virtualNetwork,omitempty"`
VirtualNetworkResourceGroup string `json:"virtualNetworkResourceGroup,omitempty" yaml:"virtualNetworkResourceGroup,omitempty"`
WindowsAdminPassword string `json:"windowsAdminPassword,omitempty" yaml:"windowsAdminPassword,omitempty"`
WindowsAdminUsername string `json:"windowsAdminUsername,omitempty" yaml:"windowsAdminUsername,omitempty"`
VirtualNetwork *string `json:"virtualNetwork,omitempty" yaml:"virtualNetwork,omitempty"`
VirtualNetworkResourceGroup *string `json:"virtualNetworkResourceGroup,omitempty" yaml:"virtualNetworkResourceGroup,omitempty"`
WindowsAdminPassword *string `json:"windowsAdminPassword,omitempty" yaml:"windowsAdminPassword,omitempty"`
WindowsAdminUsername *string `json:"windowsAdminUsername,omitempty" yaml:"windowsAdminUsername,omitempty"`
}
......@@ -25,8 +25,8 @@ type AKSNodePool struct {
MaxPods *int64 `json:"maxPods,omitempty" yaml:"maxPods,omitempty"`
MinCount *int64 `json:"minCount,omitempty" yaml:"minCount,omitempty"`
Mode string `json:"mode,omitempty" yaml:"mode,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
OrchestratorVersion string `json:"orchestratorVersion,omitempty" yaml:"orchestratorVersion,omitempty"`
Name *string `json:"name,omitempty" yaml:"name,omitempty"`
OrchestratorVersion *string `json:"orchestratorVersion,omitempty" yaml:"orchestratorVersion,omitempty"`
OsDiskSizeGB *int64 `json:"osDiskSizeGB,omitempty" yaml:"osDiskSizeGB,omitempty"`
OsDiskType string `json:"osDiskType,omitempty" yaml:"osDiskType,omitempty"`
OsType string `json:"osType,omitempty" yaml:"osType,omitempty"`
......
......@@ -19,7 +19,6 @@ func BuildAKSUpstreamSpec(secretsCache wranglerv1.SecretCache, cluster *mgmtv3.C
upstreamSpec.ClusterName = cluster.Spec.AKSConfig.ClusterName
upstreamSpec.ResourceLocation = cluster.Spec.AKSConfig.ResourceLocation
upstreamSpec.ResourceGroup = cluster.Spec.AKSConfig.ResourceGroup
upstreamSpec.TenantID = cluster.Spec.AKSConfig.TenantID
upstreamSpec.AzureCredentialSecret = cluster.Spec.AKSConfig.AzureCredentialSecret
upstreamSpec.Imported = cluster.Spec.AKSConfig.Imported
......
package capabilities
import (
"context"
"encoding/json"
"fmt"
"net/http"
"sort"
"github.com/Azure/azure-sdk-for-go/services/containerservice/mgmt/2017-09-30/containerservice"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/mcuadros/go-version"
)
func NewAKSVersionsHandler() *AKSVersionHandler {
return &AKSVersionHandler{}
}
type AKSVersionHandler struct {
}
type regionCapabilitiesRequestBody struct {
// BaseURL specifies the Azure Resource management endpoint, it defaults "https://management.azure.com/".
BaseURL string `json:"baseUrl"`
// AuthBaseURL specifies the Azure OAuth 2.0 authentication endpoint, it defaults "https://login.microsoftonline.com/".
AuthBaseURL string `json:"authBaseUrl"`
// credentials
ClientID string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
SubscriptionID string `json:"subscriptionId"`
TenantID string `json:"tenantId"`
Region string `json:"region"`
}
func (g *AKSVersionHandler) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodPost {
writer.WriteHeader(http.StatusMethodNotAllowed)
return
}
writer.Header().Set("Content-Type", "application/json")
var body regionCapabilitiesRequestBody
if err := extractRequestBody(writer, req, &body); err != nil {
handleErr(writer, err)
return
}
if err := validateRegionRequestBody(writer, &body); err != nil {
handleErr(writer, err)
return
}
region := body.Region
clientID := body.ClientID
clientSecret := body.ClientSecret
subscriptionID := body.SubscriptionID
tenantID := body.TenantID
baseURL := body.BaseURL
authBaseURL := body.AuthBaseURL
if baseURL == "" {
baseURL = azure.PublicCloud.ResourceManagerEndpoint
}
if authBaseURL == "" {
authBaseURL = azure.PublicCloud.ActiveDirectoryEndpoint
}
oAuthConfig, err := adal.NewOAuthConfig(authBaseURL, tenantID)
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
handleErr(writer, fmt.Errorf("failed to configure azure oaith: %v", err))
return
}
spToken, err := adal.NewServicePrincipalToken(*oAuthConfig, clientID, clientSecret, baseURL)
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
handleErr(writer, fmt.Errorf("failed to create token: %v", err))
return
}
authorizer := autorest.NewBearerAuthorizer(spToken)
client := containerservice.NewContainerServicesClientWithBaseURI(baseURL, subscriptionID)
client.Authorizer = authorizer
orchestrators, err := client.ListOrchestrators(context.Background(), region, "managedClusters")
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
handleErr(writer, fmt.Errorf("failed to get orchestrators: %v", err))
return
}
if orchestrators.Orchestrators == nil {
writer.WriteHeader(http.StatusBadRequest)
handleErr(writer, fmt.Errorf("no version profiles returned: %v", err))
return
}
var kubernetesVersions []string
for _, profile := range *orchestrators.Orchestrators {
if profile.OrchestratorType == nil || profile.OrchestratorVersion == nil {
writer.WriteHeader(http.StatusInternalServerError)
handleErr(writer, fmt.Errorf("unexpected nil orchestrator type or version"))
return
}
if *profile.OrchestratorType == "Kubernetes" {
kubernetesVersions = append(kubernetesVersions, *profile.OrchestratorVersion)
}
}
sort.Sort(sortableVersion(kubernetesVersions))
serialized, err := json.Marshal(kubernetesVersions)
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
handleErr(writer, err)
return
}
writer.Write(serialized)
}
type sortableVersion []string
func (s sortableVersion) Len() int {
return len(s)
}
func (s sortableVersion) Swap(a, b int) {
s[a], s[b] = s[b], s[a]
}
func (s sortableVersion) Less(a, b int) bool {
return version.Compare(s[a], s[b], "<")
}
func validateRegionRequestBody(writer http.ResponseWriter, body *regionCapabilitiesRequestBody) error {
toCheck := [][]string{
{"region", body.Region},
{"clientID", body.ClientID},
{"clientSecret", body.ClientSecret},
{"subscriptionID", body.SubscriptionID},
{"tenantID", body.TenantID},
}
for _, v := range toCheck {
if v[1] == "" {
writer.WriteHeader(http.StatusBadRequest)
return fmt.Errorf("invalid %s", v[0])
}
}
return nil
}
package capabilities
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"regexp"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-07-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
)
var regex = regexp.MustCompile("/resourceGroups/(.+?)/")
func NewAKSVirtualNetworksHandler() *AKSVirtualNetworksHandler {
return &AKSVirtualNetworksHandler{}
}
type AKSVirtualNetworksHandler struct {
}
type virtualNetworksRequestBody struct {
// BaseURL specifies the Azure Resource management endpoint, it defaults "https://management.azure.com/".
BaseURL string `json:"baseUrl"`
// AuthBaseURL specifies the Azure OAuth 2.0 authentication endpoint, it defaults "https://login.microsoftonline.com/".
AuthBaseURL string `json:"authBaseUrl"`
// credentials
ClientID string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
SubscriptionID string `json:"subscriptionId"`
TenantID string `json:"tenantId"`
}
type virtualNetworksResponseBody struct {
Name string `json:"name"`
ResourceGroup string `json:"resourceGroup"`
Subnets []subnet `json:"subnets"`
}
type subnet struct {
Name string `json:"name"`
AddressRange string `json:"addressRange"`
}
func (g *AKSVirtualNetworksHandler) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodPost {
writer.WriteHeader(http.StatusMethodNotAllowed)
return
}
writer.Header().Set("Content-Type", "application/json")
var body virtualNetworksRequestBody
if err := extractRequestBody(writer, req, &body); err != nil {
handleErr(writer, err)
return
}
if err := validateVirtualNetworksRequestBody(&body); err != nil {
writer.WriteHeader(http.StatusBadRequest)
handleErr(writer, err)
return
}
clientID := body.ClientID
clientSecret := body.ClientSecret
subscriptionID := body.SubscriptionID
tenantID := body.TenantID
baseURL := body.BaseURL
authBaseURL := body.AuthBaseURL
if baseURL == "" {
baseURL = azure.PublicCloud.ResourceManagerEndpoint
}
if authBaseURL == "" {
authBaseURL = azure.PublicCloud.ActiveDirectoryEndpoint
}
oAuthConfig, err := adal.NewOAuthConfig(authBaseURL, tenantID)
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
handleErr(writer, fmt.Errorf("failed to configure azure oauth: %v", err))
return
}
spToken, err := adal.NewServicePrincipalToken(*oAuthConfig, clientID, clientSecret, baseURL)
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
handleErr(writer, fmt.Errorf("failed to create token: %v", err))
return
}
authorizer := autorest.NewBearerAuthorizer(spToken)
client := network.NewVirtualNetworksClientWithBaseURI(baseURL, subscriptionID)
client.Authorizer = authorizer
var networks []virtualNetworksResponseBody
pointer, err := client.ListAll(context.Background())
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
handleErr(writer, fmt.Errorf("failed to get networks: %v", err))
return
}
for pointer.NotDone() {
var batch []virtualNetworksResponseBody
for _, azureNetwork := range pointer.Values() {
var subnets []subnet
if azureNetwork.Subnets != nil {
for _, azureSubnet := range *azureNetwork.Subnets {
if azureSubnet.Name != nil {
subnets = append(subnets, subnet{
Name: *azureSubnet.Name,
AddressRange: *azureSubnet.AddressPrefix,
})
}
}
}
if azureNetwork.ID == nil {
writer.WriteHeader(http.StatusInternalServerError)
handleErr(writer, errors.New("no ID on virtual network"))
return
}
match := regex.FindStringSubmatch(*azureNetwork.ID)
if len(match) < 2 || match[1] == "" {
writer.WriteHeader(http.StatusInternalServerError)
handleErr(writer, errors.New("could not parse virtual network ID"))
return
}
if azureNetwork.Name == nil {
writer.WriteHeader(http.StatusInternalServerError)
handleErr(writer, errors.New("no name on virtual network"))
return
}
batch = append(batch, virtualNetworksResponseBody{
Name: *azureNetwork.Name,
ResourceGroup: match[1],
Subnets: subnets,
})
}
networks = append(networks, batch...)
err = pointer.Next()
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
handleErr(writer, err)
return
}
}
serialized, err := json.Marshal(networks)
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
handleErr(writer, err)
return
}
writer.Write(serialized)
}
func validateVirtualNetworksRequestBody(body *virtualNetworksRequestBody) error {
if body.ClientID == "" {
return fmt.Errorf("invalid clientID")
}
if body.ClientSecret == "" {
return fmt.Errorf("invalid clientSecret")
}
if body.SubscriptionID == "" {
return fmt.Errorf("invalid subscriptionID")
}
if body.TenantID == "" {
return fmt.Errorf("invalid tenantID")
}
return nil
}
package capabilities
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/sirupsen/logrus"
)
type errorResponse struct {
Error string `json:"error"`
}
func handleErr(writer http.ResponseWriter, originalErr error) {
resp := errorResponse{originalErr.Error()}
asJSON, err := json.Marshal(resp)
if err != nil {
logrus.Error("error while marshalling error message '" + originalErr.Error() + "' error was '" + err.Error() + "'")
writer.Write([]byte(err.Error()))
return
}
writer.Write(asJSON)
}
func extractRequestBody(writer http.ResponseWriter, req *http.Request, body interface{}) error {
raw, err := ioutil.ReadAll(req.Body)
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
return fmt.Errorf("cannot read request body: " + err.Error())
}
err = json.Unmarshal(raw, &body)
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
return fmt.Errorf("cannot parse request body: " + err.Error())
}
return nil
}
......@@ -4,13 +4,11 @@ import (
"context"
"net/http"
"github.com/rancher/apiserver/pkg/parse"
"github.com/rancher/rancher/pkg/features"
"github.com/rancher/rancher/pkg/tunnelserver/mcmauthorizer"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rancher/apiserver/pkg/parse"
"github.com/rancher/rancher/pkg/api/norman"
"github.com/rancher/rancher/pkg/api/norman/customization/aks"
"github.com/rancher/rancher/pkg/api/norman/customization/clusterregistrationtokens"
"github.com/rancher/rancher/pkg/api/norman/customization/gke"
"github.com/rancher/rancher/pkg/api/norman/customization/oci"
......@@ -25,15 +23,16 @@ import (
"github.com/rancher/rancher/pkg/channelserver"
"github.com/rancher/rancher/pkg/clustermanager"
rancherdialer "github.com/rancher/rancher/pkg/dialer"
"github.com/rancher/rancher/pkg/features"
"github.com/rancher/rancher/pkg/httpproxy"
k8sProxyPkg "github.com/rancher/rancher/pkg/k8sproxy"
"github.com/rancher/rancher/pkg/metrics"
"github.com/rancher/rancher/pkg/multiclustermanager/capabilities"
"github.com/rancher/rancher/pkg/multiclustermanager/whitelist"
"github.com/rancher/rancher/pkg/pipeline/hooks"
"github.com/rancher/rancher/pkg/rbac"
"github.com/rancher/rancher/pkg/rkenodeconfigserver"
"github.com/rancher/rancher/pkg/telemetry"
"github.com/rancher/rancher/pkg/tunnelserver/mcmauthorizer"
"github.com/rancher/rancher/pkg/types/config"
"github.com/rancher/steve/pkg/auth"
)
......@@ -97,8 +96,7 @@ func router(ctx context.Context, localClusterEnabled bool, tunnelAuthorizer *mcm
authed.Use(mux.MiddlewareFunc(rbac.NewAccessControlHandler()))
authed.Use(requests.NewAuthenticatedFilter)
authed.Path("/meta/aksVersions").Handler(capabilities.NewAKSVersionsHandler())
authed.Path("/meta/aksVirtualNetworks").Handler(capabilities.NewAKSVirtualNetworksHandler())
authed.Path("/meta/{resource:aks.+}").Handler(aks.NewAKSHandler(scaledContext))
authed.Path("/meta/{resource:gke.+}").Handler(gke.NewGKEHandler(scaledContext))
authed.Path("/meta/oci/{resource}").Handler(oci.NewOCIHandler(scaledContext))
authed.Path("/meta/vsphere/{field}").Handler(vsphere.NewVsphereHandler(scaledContext))
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment