Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve authorization #152

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.16.4
controller-gen.kubebuilder.io/version: v0.16.5
name: ecsnodeclasses.karpenter.k8s.alibabacloud
spec:
group: karpenter.k8s.alibabacloud
Expand Down
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ require (
github.com/alibabacloud-go/tea v1.2.2
github.com/alibabacloud-go/tea-utils/v2 v2.0.6
github.com/alibabacloud-go/vpc-20160428/v6 v6.10.4
github.com/aliyun/aliyun-cli v0.0.0-20240925084117-158a70e275f0
github.com/awslabs/operatorpkg v0.0.0-20240805231134-67d0acfb6306
github.com/cloudpilot-ai/priceserver v0.0.0-20241011010411-15ac0e19a857
github.com/mitchellh/hashstructure/v2 v2.0.2
Expand Down Expand Up @@ -42,7 +41,7 @@ require (
github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
github.com/alibabacloud-go/tea-utils v1.3.1 // indirect
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/credentials-go v1.3.10 // indirect
github.com/aliyun/credentials-go v1.3.10
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/blendle/zapdriver v1.3.1 // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,6 @@ github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzY
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
github.com/alibabacloud-go/vpc-20160428/v6 v6.10.4 h1:6OXPOw1WcEjoSCOPFtKKgFFSlSSapns0uoOML+hzn8M=
github.com/alibabacloud-go/vpc-20160428/v6 v6.10.4/go.mod h1:6516WWE4Y9lzscVSfaev84DM+TQSvBEGX1oeMvDL5xk=
github.com/aliyun/aliyun-cli v0.0.0-20240925084117-158a70e275f0 h1:GyWC5h0inFp+vmJVbbRGh34KTj/HeG/tyqJwR+pQbko=
github.com/aliyun/aliyun-cli v0.0.0-20240925084117-158a70e275f0/go.mod h1:LRvAoBmigy35079C5FYKseCCHG4oCCC0dEgyuSsAVeo=
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
Expand Down
2 changes: 1 addition & 1 deletion pkg/operator/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ type Operator struct {
}

func NewOperator(ctx context.Context, operator *operator.Operator) (context.Context, *Operator) {
clientConfig, err := client.NewClientConfig()
clientConfig, err := client.NewClientConfig(ctx)
if err != nil {
log.FromContext(ctx).Error(err, "Failed to create client config")
os.Exit(1)
Expand Down
36 changes: 24 additions & 12 deletions pkg/utils/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,42 @@ limitations under the License.
package client

import (
"errors"
"context"
"fmt"
"os"

openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
"github.com/alibabacloud-go/tea/tea"
aliyunconfig "github.com/aliyun/aliyun-cli/config"
"github.com/aliyun/credentials-go/credentials"
"sigs.k8s.io/controller-runtime/pkg/log"

"github.com/cloudpilot-ai/karpenter-provider-alibabacloud/pkg/utils/client/metadata"
)

func NewClientConfig() (*openapi.Config, error) {
profile, err := aliyunconfig.LoadCurrentProfile()
func NewClientConfig(ctx context.Context) (*openapi.Config, error) {
// Load in the following order: 1. AK/SK, 2. RRSA, 3. config.json, 4. RAMRole
// https://www.alibabacloud.com/help/zh/sdk/developer-reference/v2-manage-go-access-credentials#3ca299f04bw3c
credential, err := credentials.NewCredential(nil)
if err != nil {
return nil, err
}

if profile.RegionId == "" {
return nil, errors.New("regionId must be set in the config file")
if cred, err := credential.GetCredential(); err == nil && cred != nil {
log.FromContext(ctx).Info(fmt.Sprintf("using credential type: %s, AccessKeyID: %s", tea.StringValue(cred.Type), tea.StringValue(cred.AccessKeyId)))
} else {
return nil, fmt.Errorf("failed get credential, error: %w", err)
}

credentialClient, err := profile.GetCredential(nil, nil)
if err != nil {
return nil, err
region := os.Getenv("REGION_ID")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to put the getenv operation at the top level and pass it here? Avoid overriding possible future logic.

if region == "" {
region, err = metadata.NewMetaData(nil).Region()
if err != nil {
return nil, err
}
}

return &openapi.Config{
RegionId: tea.String(profile.RegionId),
Credential: credentialClient,
RegionId: tea.String(region),
Credential: credential,
Network: tea.String(os.Getenv("ALIBABA_CLOUD_NETWORK")), // 1. public, 2. vpc, default is public

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

}, nil
}
257 changes: 257 additions & 0 deletions pkg/utils/client/metadata/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
/*
Copyright 2024 The CloudPilot AI Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package metadata

import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"reflect"
"strings"
"time"
)

const (
Endpoint = "http://100.100.100.200"
hostname = "hostname"
instanceID = "instance-id"
regionID = "region-id"
ramSecurity = "ram/security-credentials"
)

// MetaData wrap http client
type MetaData struct {
// mock for unit test.
mock requestMock
client *http.Client
}

// NewMetaData returns MetaData
func NewMetaData(client *http.Client) *MetaData {
if client == nil {
client = &http.Client{}
}
return &MetaData{
client: client,
}
}

// NewMockMetaData returns mock MetaData
func NewMockMetaData(client *http.Client, sendRequest requestMock) *MetaData {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this func?

if client == nil {
client = &http.Client{}
}
return &MetaData{
client: client,
mock: sendRequest,
}
}

// New returns MetaDataRequest
func (m *MetaData) New() *MetaDataRequest {
return &MetaDataRequest{
client: m.client,
sendRequest: m.mock,
}
}

// HostName returns host name
func (m *MetaData) HostName() (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should only keep what we need and delete the unused function?

var name ResultList
err := m.New().Resource(hostname).Do(&name)
if err != nil {
return "", err
}
return name.result[0], nil
}

// InstanceID returns instance Id
func (m *MetaData) InstanceID() (string, error) {
var instanceid ResultList
err := m.New().Resource(instanceID).Do(&instanceid)
if err != nil {
return "", err
}
return instanceid.result[0], err
}

// Region returns region
func (m *MetaData) Region() (string, error) {
var region ResultList
err := m.New().Resource(regionID).Do(&region)
if err != nil {
return "", err
}
return region.result[0], nil
}

// RoleName returns role name
func (m *MetaData) RoleName() (string, error) {
var roleName ResultList
err := m.New().Resource("ram/security-credentials/").Do(&roleName)
if err != nil {
return "", err
}
return roleName.result[0], nil
}

// RamRoleToken returns ram role token
func (m *MetaData) RAMRoleToken(role string) (RoleAuth, error) {
var roleauth RoleAuth
err := m.New().Resource(ramSecurity).SubResource(role).Do(&roleauth)
if err != nil {
return RoleAuth{}, err
}
return roleauth, nil
}

type requestMock func(resource string) (string, error)

// ResultList struct
type ResultList struct {
result []string
}

// nolint: stylecheck
// RoleAuth struct
type RoleAuth struct {
AccessKeyId string
AccessKeySecret string
Expiration time.Time
SecurityToken string
LastUpdated time.Time
Code string
}

// MetaDataRequest struct
type MetaDataRequest struct {
version string
resourceType string
resource string
subResource string
client *http.Client

sendRequest requestMock
}

// Version sets version
func (r *MetaDataRequest) Version(version string) *MetaDataRequest {
r.version = version
return r
}

// ResourceType sets resource type
func (r *MetaDataRequest) ResourceType(rtype string) *MetaDataRequest {
r.resourceType = rtype
return r
}

// Resource sets resource
func (r *MetaDataRequest) Resource(resource string) *MetaDataRequest {
r.resource = resource
return r
}

// SubResource set sub resource
func (r *MetaDataRequest) SubResource(sub string) *MetaDataRequest {
r.subResource = sub
return r
}

// URL returns url
func (r *MetaDataRequest) URL() (string, error) {
if r.version == "" {
r.version = "latest"
}
if r.resourceType == "" {
r.resourceType = "meta-data"
}
if r.resource == "" {
return "", errors.New("the resource you want to visit must not be nil")
}
endpoint := os.Getenv("METADATA_ENDPOINT")
if endpoint == "" {
endpoint = Endpoint
}
url := fmt.Sprintf("%s/%s/%s/%s", endpoint, r.version, r.resourceType, r.resource)
if r.subResource == "" {
return url, nil
}
return fmt.Sprintf("%s/%s", url, r.subResource), nil
}

// Do try to do MetaDataRequest
func (r *MetaDataRequest) Do(api interface{}) (err error) {
res := ""

if r.sendRequest != nil {
res, err = r.sendRequest(r.resource)
} else {
res, err = r.send()
}

if err != nil {
return err
}
return r.Decode(res, api)
}

// Decode returns decoded content
func (r *MetaDataRequest) Decode(data string, api interface{}) error {
if data == "" {
url, _ := r.URL()
return fmt.Errorf("metadata: alivpc decode data must not be nil. url=[%s]", url)
}
switch api := api.(type) {
case *ResultList:
api.result = strings.Split(data, "\n")
return nil
case *RoleAuth:
return json.Unmarshal([]byte(data), api)
default:
return fmt.Errorf("metadata: unknow type to decode, type=%s", reflect.TypeOf(api))
}
}

func (r *MetaDataRequest) send() (string, error) {
url, err := r.URL()
if err != nil {
return "", err
}
req, err := http.NewRequest(http.MethodGet, url, nil)

if err != nil {
return "", err
}
resp, err := r.client.Do(req)
if err != nil {
return "", err
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("aliyun Metadata API Error: Status Code: %d", resp.StatusCode)
}
defer resp.Body.Close()

data, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(data), nil
}
Loading
Loading