authorizer.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. package aws
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/aws/aws-sdk-go-v2/aws"
  6. awsconfig "github.com/aws/aws-sdk-go-v2/config"
  7. "github.com/aws/aws-sdk-go-v2/credentials/stscreds"
  8. "github.com/aws/aws-sdk-go-v2/service/sts"
  9. "github.com/opencost/opencost/core/pkg/util/json"
  10. "github.com/opencost/opencost/pkg/cloud"
  11. )
  12. const AccessKeyAuthorizerType = "AWSAccessKey"
  13. const ServiceAccountAuthorizerType = "AWSServiceAccount"
  14. const AssumeRoleAuthorizerType = "AWSAssumeRole"
  15. // Authorizer implementations provide aws.Config for AWS SDK calls
  16. type Authorizer interface {
  17. cloud.Authorizer
  18. CreateAWSConfig(string) (aws.Config, error)
  19. }
  20. // SelectAuthorizerByType is an implementation of AuthorizerSelectorFn and acts as a register for Authorizer types
  21. func SelectAuthorizerByType(typeStr string) (Authorizer, error) {
  22. switch typeStr {
  23. case AccessKeyAuthorizerType:
  24. return &AccessKey{}, nil
  25. case ServiceAccountAuthorizerType:
  26. return &ServiceAccount{}, nil
  27. case AssumeRoleAuthorizerType:
  28. return &AssumeRole{}, nil
  29. default:
  30. return nil, fmt.Errorf("AWS: provider authorizer type '%s' is not valid", typeStr)
  31. }
  32. }
  33. // AccessKey holds AWS credentials and fulfils the awsV2.CredentialsProvider interface
  34. type AccessKey struct {
  35. ID string `json:"id"`
  36. Secret string `json:"secret"`
  37. }
  38. // MarshalJSON custom json marshalling functions, sets properties as tagged in struct and sets the authorizer type property
  39. func (ak *AccessKey) MarshalJSON() ([]byte, error) {
  40. fmap := make(map[string]any, 3)
  41. fmap[cloud.AuthorizerTypeProperty] = AccessKeyAuthorizerType
  42. fmap["id"] = ak.ID
  43. fmap["secret"] = ak.Secret
  44. return json.Marshal(fmap)
  45. }
  46. // Retrieve returns a set of awsV2 credentials using the AccessKey's key and secret.
  47. // This fulfils the awsV2.CredentialsProvider interface contract.
  48. func (ak *AccessKey) Retrieve(ctx context.Context) (aws.Credentials, error) {
  49. return aws.Credentials{
  50. AccessKeyID: ak.ID,
  51. SecretAccessKey: ak.Secret,
  52. }, nil
  53. }
  54. func (ak *AccessKey) Validate() error {
  55. if ak.ID == "" {
  56. return fmt.Errorf("AccessKey: missing ID")
  57. }
  58. if ak.Secret == "" {
  59. return fmt.Errorf("AccessKey: missing Secret")
  60. }
  61. return nil
  62. }
  63. func (ak *AccessKey) Equals(config cloud.Config) bool {
  64. if config == nil {
  65. return false
  66. }
  67. thatConfig, ok := config.(*AccessKey)
  68. if !ok {
  69. return false
  70. }
  71. if ak.ID != thatConfig.ID {
  72. return false
  73. }
  74. if ak.Secret != thatConfig.Secret {
  75. return false
  76. }
  77. return true
  78. }
  79. func (ak *AccessKey) Sanitize() cloud.Config {
  80. return &AccessKey{
  81. ID: ak.ID,
  82. Secret: cloud.Redacted,
  83. }
  84. }
  85. // CreateAWSConfig creates an AWS SDK V2 Config for the credentials that it contains for the provided region
  86. func (ak *AccessKey) CreateAWSConfig(region string) (cfg aws.Config, err error) {
  87. err = ak.Validate()
  88. if err != nil {
  89. return cfg, err
  90. }
  91. // The AWS SDK v2 requires an object fulfilling the CredentialsProvider interface, which cloud.AccessKey does
  92. cfg, err = awsconfig.LoadDefaultConfig(context.TODO(), awsconfig.WithCredentialsProvider(ak), awsconfig.WithRegion(region))
  93. if err != nil {
  94. return cfg, fmt.Errorf("failed to initialize AWS SDK config for region %s: %s", region, err)
  95. }
  96. return cfg, nil
  97. }
  98. // ServiceAccount uses pod annotations along with a service account to authenticate integrations
  99. type ServiceAccount struct{}
  100. // MarshalJSON custom json marshalling functions, sets properties as tagged in struct and sets the authorizer type property
  101. func (sa *ServiceAccount) MarshalJSON() ([]byte, error) {
  102. fmap := make(map[string]any, 1)
  103. fmap[cloud.AuthorizerTypeProperty] = ServiceAccountAuthorizerType
  104. return json.Marshal(fmap)
  105. }
  106. // Check has nothing to check at this level, connection will fail if Pod Annotation and Service Account are not configured correctly
  107. func (sa *ServiceAccount) Validate() error {
  108. return nil
  109. }
  110. func (sa *ServiceAccount) Equals(config cloud.Config) bool {
  111. if config == nil {
  112. return false
  113. }
  114. _, ok := config.(*ServiceAccount)
  115. if !ok {
  116. return false
  117. }
  118. return true
  119. }
  120. func (sa *ServiceAccount) Sanitize() cloud.Config {
  121. return &ServiceAccount{}
  122. }
  123. func (sa *ServiceAccount) CreateAWSConfig(region string) (aws.Config, error) {
  124. cfg, err := awsconfig.LoadDefaultConfig(context.TODO(), awsconfig.WithRegion(region))
  125. if err != nil {
  126. return cfg, fmt.Errorf("failed to initialize AWS SDK config for region from annotation %s: %s", region, err)
  127. }
  128. return cfg, nil
  129. }
  130. // AssumeRole is a wrapper for another Authorizer which adds an assumed role to the configuration
  131. type AssumeRole struct {
  132. Authorizer Authorizer `json:"authorizer"`
  133. RoleARN string `json:"roleARN"`
  134. }
  135. // MarshalJSON custom json marshalling functions, sets properties as tagged in struct and sets the authorizer type property
  136. func (ara *AssumeRole) MarshalJSON() ([]byte, error) {
  137. fmap := make(map[string]any, 3)
  138. fmap[cloud.AuthorizerTypeProperty] = AssumeRoleAuthorizerType
  139. fmap["roleARN"] = ara.RoleARN
  140. fmap["authorizer"] = ara.Authorizer
  141. return json.Marshal(fmap)
  142. }
  143. // UnmarshalJSON is required for AssumeRole because it needs to unmarshal an Authorizer interface
  144. func (ara *AssumeRole) UnmarshalJSON(b []byte) error {
  145. var f interface{}
  146. err := json.Unmarshal(b, &f)
  147. if err != nil {
  148. return err
  149. }
  150. fmap := f.(map[string]interface{})
  151. roleARN, err := cloud.GetInterfaceValue[string](fmap, "roleARN")
  152. if err != nil {
  153. return fmt.Errorf("StorageConfiguration: UnmarshalJSON: %s", err.Error())
  154. }
  155. ara.RoleARN = roleARN
  156. authAny, ok := fmap["authorizer"]
  157. if !ok {
  158. return fmt.Errorf("AssumeRole: UnmarshalJSON: missing Authorizer")
  159. }
  160. authorizer, err := cloud.AuthorizerFromInterface(authAny, SelectAuthorizerByType)
  161. if err != nil {
  162. return fmt.Errorf("AssumeRole: UnmarshalJSON: %s", err.Error())
  163. }
  164. ara.Authorizer = authorizer
  165. return nil
  166. }
  167. func (ara *AssumeRole) CreateAWSConfig(region string) (aws.Config, error) {
  168. cfg, _ := ara.Authorizer.CreateAWSConfig(region)
  169. // Create the credentials from AssumeRoleProvider to assume the role
  170. // referenced by the RoleARN.
  171. stsSvc := sts.NewFromConfig(cfg)
  172. creds := stscreds.NewAssumeRoleProvider(stsSvc, ara.RoleARN)
  173. cfg.Credentials = aws.NewCredentialsCache(creds)
  174. return cfg, nil
  175. }
  176. func (ara *AssumeRole) Validate() error {
  177. if ara.Authorizer == nil {
  178. return fmt.Errorf("AssumeRole: misisng base Authorizer")
  179. }
  180. err := ara.Authorizer.Validate()
  181. if err != nil {
  182. return err
  183. }
  184. if ara.RoleARN == "" {
  185. return fmt.Errorf("AssumeRole: misisng RoleARN configuration")
  186. }
  187. return nil
  188. }
  189. func (ara *AssumeRole) Equals(config cloud.Config) bool {
  190. if config == nil {
  191. return false
  192. }
  193. thatConfig, ok := config.(*AssumeRole)
  194. if !ok {
  195. return false
  196. }
  197. if ara.Authorizer != nil {
  198. if !ara.Authorizer.Equals(thatConfig.Authorizer) {
  199. return false
  200. }
  201. } else {
  202. if thatConfig.Authorizer != nil {
  203. return false
  204. }
  205. }
  206. if ara.RoleARN != thatConfig.RoleARN {
  207. return false
  208. }
  209. return true
  210. }
  211. func (ara *AssumeRole) Sanitize() cloud.Config {
  212. return &AssumeRole{
  213. Authorizer: ara.Authorizer.Sanitize().(Authorizer),
  214. RoleARN: ara.RoleARN,
  215. }
  216. }