Bladeren bron

Add AWS web identity cloud authorizer

Signed-off-by: Kaelan Patel <32113845+kaelanspatel@users.noreply.github.com>
Kaelan Patel 2 jaren geleden
bovenliggende
commit
ec4b77dd9c
2 gewijzigde bestanden met toevoegingen van 106 en 7 verwijderingen
  1. 92 7
      pkg/cloud/aws/authorizer.go
  2. 14 0
      pkg/cloud/aws/authorizer_test.go

+ 92 - 7
pkg/cloud/aws/authorizer.go

@@ -10,11 +10,15 @@ import (
 	"github.com/aws/aws-sdk-go-v2/service/sts"
 	"github.com/opencost/opencost/core/pkg/util/json"
 	"github.com/opencost/opencost/pkg/cloud"
+	"golang.org/x/oauth2/google"
+	"google.golang.org/api/idtoken"
+	"google.golang.org/api/option"
 )
 
 const AccessKeyAuthorizerType = "AWSAccessKey"
 const ServiceAccountAuthorizerType = "AWSServiceAccount"
 const AssumeRoleAuthorizerType = "AWSAssumeRole"
+const GoogleWebIdentityAuthorizerType = "GoogleWebIdentity"
 
 // Authorizer implementations provide aws.Config for AWS SDK calls
 type Authorizer interface {
@@ -129,11 +133,7 @@ func (sa *ServiceAccount) Equals(config cloud.Config) bool {
 		return false
 	}
 	_, ok := config.(*ServiceAccount)
-	if !ok {
-		return false
-	}
-
-	return true
+	return ok
 }
 
 func (sa *ServiceAccount) Sanitize() cloud.Config {
@@ -204,7 +204,7 @@ func (ara *AssumeRole) CreateAWSConfig(region string) (aws.Config, error) {
 
 func (ara *AssumeRole) Validate() error {
 	if ara.Authorizer == nil {
-		return fmt.Errorf("AssumeRole: misisng base Authorizer")
+		return fmt.Errorf("AssumeRole: missing base Authorizer")
 	}
 	err := ara.Authorizer.Validate()
 	if err != nil {
@@ -212,7 +212,7 @@ func (ara *AssumeRole) Validate() error {
 	}
 
 	if ara.RoleARN == "" {
-		return fmt.Errorf("AssumeRole: misisng RoleARN configuration")
+		return fmt.Errorf("AssumeRole: missing RoleARN configuration")
 	}
 
 	return nil
@@ -249,3 +249,88 @@ func (ara *AssumeRole) Sanitize() cloud.Config {
 		RoleARN:    ara.RoleARN,
 	}
 }
+
+type GoogleWebIdentity struct {
+	RoleARN        string                 `json:"roleARN"`
+	TokenRetriever GoogleIDTokenRetriever `json:"tokenRetriever"`
+}
+
+type GoogleIDTokenRetriever struct {
+	Aud string `json:"aud"`
+}
+
+func (gitr *GoogleIDTokenRetriever) GetIdentityToken() ([]byte, error) {
+	ctx := context.Background()
+	res := []byte{}
+
+	credentials, err := google.FindDefaultCredentials(ctx)
+	if err != nil {
+		return res, fmt.Errorf("failed to find default credentials: %v", err)
+	}
+
+	ts, err := idtoken.NewTokenSource(ctx, gitr.Aud, option.WithCredentials(credentials))
+	if err != nil {
+		return res, fmt.Errorf("failed to create ID token source: %w", err)
+	}
+
+	t, err := ts.Token()
+	if err != nil {
+		return res, fmt.Errorf("failed to receive ID token from metadata server: %w", err)
+	}
+
+	return []byte(t.AccessToken), nil
+}
+
+func (wea *GoogleWebIdentity) CreateAWSConfig(region string) (aws.Config, error) {
+	cfg, err := awsconfig.LoadDefaultConfig(context.TODO(), awsconfig.WithRegion(region))
+	if err != nil {
+		return cfg, fmt.Errorf("failed to initialize AWS SDK config for region from annotation %s: %s", region, err)
+	}
+
+	stsSvc := sts.NewFromConfig(cfg)
+	creds := stscreds.NewWebIdentityRoleProvider(stsSvc, wea.RoleARN, &wea.TokenRetriever)
+
+	cfg.Credentials = aws.NewCredentialsCache(creds)
+	return cfg, nil
+}
+
+func (wea *GoogleWebIdentity) MarshalJSON() ([]byte, error) {
+	fmap := make(map[string]any, 1)
+	fmap[cloud.AuthorizerTypeProperty] = GoogleWebIdentityAuthorizerType
+	fmap["roleARN"] = wea.RoleARN
+	fmap["tokenRetriever"] = wea.TokenRetriever
+	return json.Marshal(fmap)
+}
+
+func (wea *GoogleWebIdentity) Validate() error {
+	if wea.TokenRetriever.Aud == "" {
+		return fmt.Errorf("GoogleWebIdenity: missing token retriver audience")
+	}
+
+	if wea.RoleARN == "" {
+		return fmt.Errorf("GoogleWebIdenity: missing RoleARN configuration")
+	}
+
+	return nil
+}
+
+func (wea *GoogleWebIdentity) Equals(config cloud.Config) bool {
+	if config == nil {
+		return false
+	}
+	thatConfig, ok := config.(*GoogleWebIdentity)
+	if !ok {
+		return false
+	}
+
+	return wea.RoleARN == thatConfig.RoleARN && wea.TokenRetriever.Aud == thatConfig.TokenRetriever.Aud
+}
+
+func (wea *GoogleWebIdentity) Sanitize() cloud.Config {
+	return &GoogleWebIdentity{
+		RoleARN: wea.RoleARN,
+		TokenRetriever: GoogleIDTokenRetriever{
+			Aud: wea.TokenRetriever.Aud,
+		},
+	}
+}

+ 14 - 0
pkg/cloud/aws/authorizer_test.go

@@ -52,6 +52,20 @@ func TestAuthorizerJSON_Sanitize(t *testing.T) {
 				RoleARN:    "role arn",
 			},
 		},
+		"Google Web Identity": {
+			input: &GoogleWebIdentity{
+				RoleARN: "role arn",
+				TokenRetriever: GoogleIDTokenRetriever{
+					Aud: "aud",
+				},
+			},
+			expected: &GoogleWebIdentity{
+				RoleARN: "role arn",
+				TokenRetriever: GoogleIDTokenRetriever{
+					Aud: "aud",
+				},
+			},
+		},
 	}
 	for name, tc := range testCases {
 		t.Run(name, func(t *testing.T) {