diff --git a/go.mod b/go.mod index a59554a375e02b6caa674d34116a6a2183db33a8..a448f6cf6b50cb8adcf68dfb1b2d60238f3c3d60 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 6e769a23a249befa69e9e99dd927142c9337438d..4387b4bf1ead88c1db6e283fb46532c7353c10f2 100644 --- a/go.sum +++ b/go.sum @@ -949,8 +949,8 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA= github.com/quobyte/api v0.1.8/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI= github.com/rackspace/gophercloud v0.0.0-20150408191457-ce0f487f6747/go.mod h1:4bJ1FwuaBZ6dt1VcDX5/O662mwR8GWqS4l68H6hkoYQ= -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/apiserver v0.0.0-20201023000256-1a0a904f9197/go.mod h1:8W0EwaR9dH5NDFw6mpAX437D0q+EZqKWbZyX71+z2WI= github.com/rancher/apiserver v0.0.0-20210519053359-f943376c4b42 h1:yjpulamf2dZz62++nQQaFjZv+qv5aR4Uwm3uj39AJ8Y= github.com/rancher/apiserver v0.0.0-20210519053359-f943376c4b42/go.mod h1:8W0EwaR9dH5NDFw6mpAX437D0q+EZqKWbZyX71+z2WI= diff --git a/pkg/api/norman/customization/aks/handler.go b/pkg/api/norman/customization/aks/handler.go new file mode 100644 index 0000000000000000000000000000000000000000..d2888a09c54ff3f767a1afe9075feeaff79dca05 --- /dev/null +++ b/pkg/api/norman/customization/aks/handler.go @@ -0,0 +1,223 @@ +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) +} diff --git a/pkg/api/norman/customization/aks/listers.go b/pkg/api/norman/customization/aks/listers.go new file mode 100644 index 0000000000000000000000000000000000000000..875b7aff9bc1acdf04c2e8002fa4a892fcbd32bd --- /dev/null +++ b/pkg/api/norman/customization/aks/listers.go @@ -0,0 +1,188 @@ +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 +} diff --git a/pkg/apis/go.mod b/pkg/apis/go.mod index 776cb937c525d0c606203895c14808ba0e9709f2..18ca7784f98ec3f5822d8e893533b8d8f3c876e1 100644 --- a/pkg/apis/go.mod +++ b/pkg/apis/go.mod @@ -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 diff --git a/pkg/apis/go.sum b/pkg/apis/go.sum index a88d4f444c2ada6f2a213fc738a4c9ee24c07498..4fd13756c1ae1b72a7a2f918db41cb4c32c12664 100644 --- a/pkg/apis/go.sum +++ b/pkg/apis/go.sum @@ -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= diff --git a/pkg/client/generated/management/v3/zz_generated_aks_cluster_config_spec.go b/pkg/client/generated/management/v3/zz_generated_aks_cluster_config_spec.go index 0565ce55628b592a5b7ce733dd647c1ea113cc16..59343ea74d43677aed4bfebd34f730ef5ff0a661 100644 --- a/pkg/client/generated/management/v3/zz_generated_aks_cluster_config_spec.go +++ b/pkg/client/generated/management/v3/zz_generated_aks_cluster_config_spec.go @@ -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"` } diff --git a/pkg/client/generated/management/v3/zz_generated_aks_node_pool.go b/pkg/client/generated/management/v3/zz_generated_aks_node_pool.go index 67aaf4aec546fd3219865784f7b709b1f5ac3592..eaf4c3f4a05f096bd7c1876c537cbb40453ab281 100644 --- a/pkg/client/generated/management/v3/zz_generated_aks_node_pool.go +++ b/pkg/client/generated/management/v3/zz_generated_aks_node_pool.go @@ -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"` diff --git a/pkg/controllers/management/clusterupstreamrefresher/aks_upstream_spec.go b/pkg/controllers/management/clusterupstreamrefresher/aks_upstream_spec.go index 8b5a27f80b9c3db1ce2e22a66698364d85a706d2..d462f6703dbcd1076f702d008a53ef763acd6b59 100644 --- a/pkg/controllers/management/clusterupstreamrefresher/aks_upstream_spec.go +++ b/pkg/controllers/management/clusterupstreamrefresher/aks_upstream_spec.go @@ -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 diff --git a/pkg/multiclustermanager/capabilities/aks_version_endpoint.go b/pkg/multiclustermanager/capabilities/aks_version_endpoint.go deleted file mode 100644 index 99e4ccb52e58e24c1c100088405df2a33f6dedc2..0000000000000000000000000000000000000000 --- a/pkg/multiclustermanager/capabilities/aks_version_endpoint.go +++ /dev/null @@ -1,161 +0,0 @@ -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 -} diff --git a/pkg/multiclustermanager/capabilities/aks_virtual_networks_endpoint.go b/pkg/multiclustermanager/capabilities/aks_virtual_networks_endpoint.go deleted file mode 100644 index 9fa7dbf0873b4343a2f6dd85dbb2e531646f35ad..0000000000000000000000000000000000000000 --- a/pkg/multiclustermanager/capabilities/aks_virtual_networks_endpoint.go +++ /dev/null @@ -1,193 +0,0 @@ -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 -} diff --git a/pkg/multiclustermanager/capabilities/http_request_body.go b/pkg/multiclustermanager/capabilities/http_request_body.go deleted file mode 100644 index 3a2126738f9dc66047b2bc9087d0ba3a4abf1d4d..0000000000000000000000000000000000000000 --- a/pkg/multiclustermanager/capabilities/http_request_body.go +++ /dev/null @@ -1,47 +0,0 @@ -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 -} diff --git a/pkg/multiclustermanager/routes.go b/pkg/multiclustermanager/routes.go index 3df463bd195bb2da919856414c030178d3f81b73..bb47aab6c252ad6039a6e3a18c9836a3ace32ee2 100644 --- a/pkg/multiclustermanager/routes.go +++ b/pkg/multiclustermanager/routes.go @@ -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))