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

feat: support identity verification through STS #164

Merged
merged 1 commit into from
Mar 15, 2024
Merged
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.19

require (
github.com/hashicorp/terraform-plugin-sdk v1.4.0
github.com/mitchellh/mapstructure v1.1.2
github.com/pkg/errors v0.8.0
github.com/tidwall/gjson v1.16.0
github.com/ucloud/ucloud-sdk-go v0.22.5
Expand Down Expand Up @@ -53,7 +54,6 @@ require (
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.1.2 // indirect
github.com/mitchellh/reflectwalk v1.0.1 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/pierrec/lz4 v2.0.5+incompatible // indirect
Expand Down
61 changes: 52 additions & 9 deletions ucloud/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ package ucloud
import (
"encoding/json"
"fmt"
"github.com/ucloud/ucloud-sdk-go/services/iam"
"github.com/ucloud/ucloud-sdk-go/services/uads"
"github.com/ucloud/ucloud-sdk-go/services/uphost"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"time"

"github.com/ucloud/ucloud-sdk-go/services/iam"
"github.com/ucloud/ucloud-sdk-go/services/uads"
"github.com/ucloud/ucloud-sdk-go/services/uphost"

"github.com/ucloud/ucloud-sdk-go/services/cube"
"github.com/ucloud/ucloud-sdk-go/services/ufile"
"github.com/ucloud/ucloud-sdk-go/services/ufs"
Expand All @@ -23,6 +24,7 @@ import (
"github.com/ucloud/ucloud-sdk-go/private/protocol/http"
pumem "github.com/ucloud/ucloud-sdk-go/private/services/umem"
"github.com/ucloud/ucloud-sdk-go/services/ipsecvpn"
"github.com/ucloud/ucloud-sdk-go/services/sts"
"github.com/ucloud/ucloud-sdk-go/services/uaccount"
"github.com/ucloud/ucloud-sdk-go/services/udb"
"github.com/ucloud/ucloud-sdk-go/services/udisk"
Expand All @@ -42,12 +44,20 @@ type Config struct {
PrivateKey string
Profile string
SharedCredentialsFile string
AssumeRole *AssumeRoleConfig
Region string
ProjectId string
Insecure bool
BaseURL string
MaxRetries int
}

Region string
ProjectId string
Insecure bool
BaseURL string
MaxRetries int
// AssumeRoleConfig is the configuration of assume role
type AssumeRoleConfig struct {
Duration time.Duration
RoleURN string
Policy string
SessionName string
}

type cloudShellCredential struct {
Expand Down Expand Up @@ -124,7 +134,14 @@ func (c *Config) Client() (*UCloudClient, error) {
} else {
return nil, fmt.Errorf("must set credential about public_key and private_key")
}

if c.AssumeRole != nil {
// get STS credential
stsCredential, err := getSTSCredential(*c.AssumeRole, cfg, cred)
if err != nil {
return nil, fmt.Errorf("fail to get STS credential, %w", err)
}
cred = *stsCredential
}
// initialize client connections
client.unetconn = unet.NewClient(&cfg, &cred)
client.ulbconn = ulb.NewClient(&cfg, &cred)
Expand Down Expand Up @@ -209,3 +226,29 @@ func userHomeDir() string {
}
return os.Getenv("HOME")
}

func getSTSCredential(assumeRole AssumeRoleConfig, config ucloud.Config, credential auth.Credential) (*auth.Credential, error) {
// get STS credential
stsClient := sts.NewClient(&config, &credential)
var assumeRoleRequest sts.AssumeRoleRequest
assumeRoleRequest.Policy = ucloud.String(assumeRole.Policy)
assumeRoleRequest.RoleUrn = ucloud.String(assumeRole.RoleURN)
assumeRoleRequest.RoleSessionName = ucloud.String(assumeRole.SessionName)
assumeRoleRequest.DurationSeconds = ucloud.Int(int(assumeRole.Duration.Seconds()))
assumeRoleResponse, err := stsClient.AssumeRole(&assumeRoleRequest)
if err != nil {
return nil, fmt.Errorf("fail to assume role, %w", err)
}
// set STS credential
var stsCredential auth.Credential
stsCredential.PublicKey = assumeRoleResponse.Credentials.AccessKeyId
stsCredential.PrivateKey = assumeRoleResponse.Credentials.AccessKeySecret
stsCredential.SecurityToken = assumeRoleResponse.Credentials.SecurityToken
stsCredential.CanExpire = true
expireTime, err := time.Parse(time.RFC3339, assumeRoleResponse.Credentials.Expiration)
if err != nil {
return nil, fmt.Errorf("fail to parse expiration time, %w", err)
}
stsCredential.Expires = expireTime
return &stsCredential, nil
}
71 changes: 71 additions & 0 deletions ucloud/provider.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package ucloud

import (
"time"

"github.com/hashicorp/terraform-plugin-sdk/helper/mutexkv"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

Expand Down Expand Up @@ -73,6 +76,8 @@ func Provider() terraform.ResourceProvider {
ConflictsWith: []string{"insecure"},
ValidateFunc: validateBaseUrl,
},

"assume_role": assumeRoleSchema(),
},

DataSourcesMap: map[string]*schema.Resource{
Expand Down Expand Up @@ -185,10 +190,76 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config.BaseURL = GetEndpointURL(config.Region)
}

if v, ok := d.GetOk("assume_role"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
config.AssumeRole = expandAssumeRole(v.([]interface{})[0].(map[string]interface{}))
}

client, err := config.Client()
return client, err
}

func assumeRoleSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"duration": {
Type: schema.TypeString,
Optional: true,
Description: "The duration of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.",
ValidateFunc: validateAssumeRoleDuration,
Default: "900s",
},
"policy": {
Type: schema.TypeString,
Optional: true,
Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.",
ValidateFunc: validation.ValidateJsonString,
},
"role_urn": {
Type: schema.TypeString,
Required: true,
Description: "UCloud Resource Name (URN) of an IAM Role to assume prior to making API calls.",
},
"session_name": {
Type: schema.TypeString,
Required: true,
Description: "An identifier for the assumed role session.",
},
},
},
}
}

func expandAssumeRole(tfMap map[string]interface{}) *AssumeRoleConfig {
if tfMap == nil {
return nil
}

assumeRole := AssumeRoleConfig{}

if v, ok := tfMap["duration"].(string); ok && v != "" {
duration, _ := time.ParseDuration(v)
assumeRole.Duration = duration
}

if v, ok := tfMap["policy"].(string); ok && v != "" {
assumeRole.Policy = v
}

if v, ok := tfMap["role_urn"].(string); ok && v != "" {
assumeRole.RoleURN = v
}

if v, ok := tfMap["session_name"].(string); ok && v != "" {
assumeRole.SessionName = v
}

return &assumeRole
}

var ucloudMutexKV = mutexkv.NewMutexKV()

var descriptions map[string]string
Expand Down
13 changes: 13 additions & 0 deletions ucloud/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"regexp"
"strconv"
"strings"
"time"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
Expand Down Expand Up @@ -340,3 +341,15 @@ func validateBaseUrl(v interface{}, k string) (ws []string, errors []error) {
}

var validateToaID = validation.IntBetween(0, 254)

// validateAssumeRoleDuration validates a string can be parsed as a valid time.Duration
func validateAssumeRoleDuration(v interface{}, k string) (ws []string, errors []error) {
_, err := time.ParseDuration(v.(string))

if err != nil {
errors = append(errors, fmt.Errorf("%q cannot be parsed as a duration: %w", k, err))
return
}

return
}
66 changes: 66 additions & 0 deletions vendor/github.com/ucloud/ucloud-sdk-go/services/sts/apis.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions vendor/github.com/ucloud/ucloud-sdk-go/services/sts/client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions vendor/github.com/ucloud/ucloud-sdk-go/services/sts/doc.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions vendor/github.com/ucloud/ucloud-sdk-go/services/sts/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ github.com/ucloud/ucloud-sdk-go/private/utils
github.com/ucloud/ucloud-sdk-go/services/cube
github.com/ucloud/ucloud-sdk-go/services/iam
github.com/ucloud/ucloud-sdk-go/services/ipsecvpn
github.com/ucloud/ucloud-sdk-go/services/sts
github.com/ucloud/ucloud-sdk-go/services/uaccount
github.com/ucloud/ucloud-sdk-go/services/uads
github.com/ucloud/ucloud-sdk-go/services/udb
Expand Down
11 changes: 11 additions & 0 deletions website/docs/index.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,17 @@ In addition to [generic `provider` arguments](https://www.terraform.io/docs/conf
* `base_url` - (Optional) This is the base url.(Default: `https://api.ucloud.cn`).
~> **Note** this argument conflicts with `insecure`.

* `assume_role` - (Optional) Configuration block for assuming an IAM role. See the [`assume_role` Configuration Block](#assume_role-configuration-block) section below. Only one `assume_role` block may be in the configuration.

### assume_role Configuration Block

The `assume_role` configuration block supports the following arguments:

* `duration` - (Optional) Duration of the assume role session. Represented by a string such as `1h`, `2h45m`, or `30m15s`. Default is `900s`. The maximum value is the maximum session duration of the role
* `policy` - (Optional) IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.
* `role_urn` - (Required) URN of the IAM Role to assume.
* `session_name` - (Required) Session name to use when assuming the role. The value can contain 1 to 64 letters, digits, underscores (_), hyphens (-), and periods (.).

## Testing

Credentials must be provided via the `UCLOUD_PUBLIC_KEY`, `UCLOUD_PRIVATE_KEY`, `UCLOUD_PROJECT_ID` environment variables in order to run acceptance tests.
Loading