| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- package aws
- import (
- "context"
- "fmt"
- "github.com/aws/aws-sdk-go-v2/aws"
- awsconfig "github.com/aws/aws-sdk-go-v2/config"
- "github.com/aws/aws-sdk-go-v2/credentials/stscreds"
- "github.com/aws/aws-sdk-go-v2/service/sts"
- "github.com/opencost/opencost/pkg/cloud"
- "github.com/opencost/opencost/pkg/util/json"
- )
- const AccessKeyAuthorizerType = "AWSAccessKey"
- const ServiceAccountAuthorizerType = "AWSServiceAccount"
- const AssumeRoleAuthorizerType = "AWSAssumeRole"
- // Authorizer implementations provide aws.Config for AWS SDK calls
- type Authorizer interface {
- cloud.Authorizer
- CreateAWSConfig(string) (aws.Config, error)
- }
- // SelectAuthorizerByType is an implementation of AuthorizerSelectorFn and acts as a register for Authorizer types
- func SelectAuthorizerByType(typeStr string) (Authorizer, error) {
- switch typeStr {
- case AccessKeyAuthorizerType:
- return &AccessKey{}, nil
- case ServiceAccountAuthorizerType:
- return &ServiceAccount{}, nil
- case AssumeRoleAuthorizerType:
- return &AssumeRole{}, nil
- default:
- return nil, fmt.Errorf("AWS: provider authorizer type '%s' is not valid", typeStr)
- }
- }
- // AccessKey holds AWS credentials and fulfils the awsV2.CredentialsProvider interface
- type AccessKey struct {
- ID string `json:"id"`
- Secret string `json:"secret"`
- }
- // MarshalJSON custom json marshalling functions, sets properties as tagged in struct and sets the authorizer type property
- func (ak *AccessKey) MarshalJSON() ([]byte, error) {
- fmap := make(map[string]any, 3)
- fmap[cloud.AuthorizerTypeProperty] = AccessKeyAuthorizerType
- fmap["id"] = ak.ID
- fmap["secret"] = ak.Secret
- return json.Marshal(fmap)
- }
- // Retrieve returns a set of awsV2 credentials using the AccessKey's key and secret.
- // This fulfils the awsV2.CredentialsProvider interface contract.
- func (ak *AccessKey) Retrieve(ctx context.Context) (aws.Credentials, error) {
- return aws.Credentials{
- AccessKeyID: ak.ID,
- SecretAccessKey: ak.Secret,
- }, nil
- }
- func (ak *AccessKey) Validate() error {
- if ak.ID == "" {
- return fmt.Errorf("AccessKey: missing ID")
- }
- if ak.Secret == "" {
- return fmt.Errorf("AccessKey: missing Secret")
- }
- return nil
- }
- func (ak *AccessKey) Equals(config cloud.Config) bool {
- if config == nil {
- return false
- }
- thatConfig, ok := config.(*AccessKey)
- if !ok {
- return false
- }
- if ak.ID != thatConfig.ID {
- return false
- }
- if ak.Secret != thatConfig.Secret {
- return false
- }
- return true
- }
- func (ak *AccessKey) Sanitize() cloud.Config {
- return &AccessKey{
- ID: ak.ID,
- Secret: cloud.Redacted,
- }
- }
- // CreateAWSConfig creates an AWS SDK V2 Config for the credentials that it contains for the provided region
- func (ak *AccessKey) CreateAWSConfig(region string) (cfg aws.Config, err error) {
- err = ak.Validate()
- if err != nil {
- return cfg, err
- }
- // The AWS SDK v2 requires an object fulfilling the CredentialsProvider interface, which cloud.AccessKey does
- cfg, err = awsconfig.LoadDefaultConfig(context.TODO(), awsconfig.WithCredentialsProvider(ak), awsconfig.WithRegion(region))
- if err != nil {
- return cfg, fmt.Errorf("failed to initialize AWS SDK config for region %s: %s", region, err)
- }
- return cfg, nil
- }
- // ServiceAccount uses pod annotations along with a service account to authenticate integrations
- type ServiceAccount struct{}
- // MarshalJSON custom json marshalling functions, sets properties as tagged in struct and sets the authorizer type property
- func (sa *ServiceAccount) MarshalJSON() ([]byte, error) {
- fmap := make(map[string]any, 1)
- fmap[cloud.AuthorizerTypeProperty] = ServiceAccountAuthorizerType
- return json.Marshal(fmap)
- }
- // Check has nothing to check at this level, connection will fail if Pod Annotation and Service Account are not configured correctly
- func (sa *ServiceAccount) Validate() error {
- return nil
- }
- func (sa *ServiceAccount) Equals(config cloud.Config) bool {
- if config == nil {
- return false
- }
- _, ok := config.(*ServiceAccount)
- if !ok {
- return false
- }
- return true
- }
- func (sa *ServiceAccount) Sanitize() cloud.Config {
- return &ServiceAccount{}
- }
- func (sa *ServiceAccount) 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)
- }
- return cfg, nil
- }
- // AssumeRole is a wrapper for another Authorizer which adds an assumed role to the configuration
- type AssumeRole struct {
- Authorizer Authorizer `json:"authorizer"`
- RoleARN string `json:"roleARN"`
- }
- // MarshalJSON custom json marshalling functions, sets properties as tagged in struct and sets the authorizer type property
- func (ara *AssumeRole) MarshalJSON() ([]byte, error) {
- fmap := make(map[string]any, 3)
- fmap[cloud.AuthorizerTypeProperty] = AssumeRoleAuthorizerType
- fmap["roleARN"] = ara.RoleARN
- fmap["authorizer"] = ara.Authorizer
- return json.Marshal(fmap)
- }
- // UnmarshalJSON is required for AssumeRole because it needs to unmarshal an Authorizer interface
- func (ara *AssumeRole) UnmarshalJSON(b []byte) error {
- var f interface{}
- err := json.Unmarshal(b, &f)
- if err != nil {
- return err
- }
- fmap := f.(map[string]interface{})
- roleARN, err := cloud.GetInterfaceValue[string](fmap, "roleARN")
- if err != nil {
- return fmt.Errorf("StorageConfiguration: UnmarshalJSON: %s", err.Error())
- }
- ara.RoleARN = roleARN
- authAny, ok := fmap["authorizer"]
- if !ok {
- return fmt.Errorf("AssumeRole: UnmarshalJSON: missing Authorizer")
- }
- authorizer, err := cloud.AuthorizerFromInterface(authAny, SelectAuthorizerByType)
- if err != nil {
- return fmt.Errorf("AssumeRole: UnmarshalJSON: %s", err.Error())
- }
- ara.Authorizer = authorizer
- return nil
- }
- func (ara *AssumeRole) CreateAWSConfig(region string) (aws.Config, error) {
- cfg, _ := ara.Authorizer.CreateAWSConfig(region)
- // Create the credentials from AssumeRoleProvider to assume the role
- // referenced by the RoleARN.
- stsSvc := sts.NewFromConfig(cfg)
- creds := stscreds.NewAssumeRoleProvider(stsSvc, ara.RoleARN)
- cfg.Credentials = aws.NewCredentialsCache(creds)
- return cfg, nil
- }
- func (ara *AssumeRole) Validate() error {
- if ara.Authorizer == nil {
- return fmt.Errorf("AssumeRole: misisng base Authorizer")
- }
- err := ara.Authorizer.Validate()
- if err != nil {
- return err
- }
- if ara.RoleARN == "" {
- return fmt.Errorf("AssumeRole: misisng RoleARN configuration")
- }
- return nil
- }
- func (ara *AssumeRole) Equals(config cloud.Config) bool {
- if config == nil {
- return false
- }
- thatConfig, ok := config.(*AssumeRole)
- if !ok {
- return false
- }
- if ara.Authorizer != nil {
- if !ara.Authorizer.Equals(thatConfig.Authorizer) {
- return false
- }
- } else {
- if thatConfig.Authorizer != nil {
- return false
- }
- }
- if ara.RoleARN != thatConfig.RoleARN {
- return false
- }
- return true
- }
- func (ara *AssumeRole) Sanitize() cloud.Config {
- return &AssumeRole{
- Authorizer: ara.Authorizer.Sanitize().(Authorizer),
- RoleARN: ara.RoleARN,
- }
- }
|