From 3b09a6166f6612a80c2d20e144a108f281cfa898 Mon Sep 17 00:00:00 2001 From: Ved misra <47312748+misraved@users.noreply.github.com> Date: Mon, 23 Oct 2023 23:15:38 +0800 Subject: [PATCH] Add tables `scaleway_iam_api_key` and `scaleway_iam_user` (#25) (#30) Signed-off-by: jplanckeel Co-authored-by: Jeremy PLANCKEEL <78090694+jplanckeel@users.noreply.github.com> Co-authored-by: jplanckeel Co-authored-by: madhushreeray@30 --- config/scaleway.spc | 4 + docs/index.md | 8 +- docs/tables/scaleway_iam_api_key.md | 36 +++++ docs/tables/scaleway_iam_user.md | 59 ++++++++ go.sum | 2 + scaleway/connection_config.go | 10 +- scaleway/plugin.go | 2 + scaleway/table_scaleway_iam_api_key.go | 194 +++++++++++++++++++++++++ scaleway/table_scaleway_iam_user.go | 189 ++++++++++++++++++++++++ 9 files changed, 499 insertions(+), 5 deletions(-) create mode 100644 docs/tables/scaleway_iam_api_key.md create mode 100644 docs/tables/scaleway_iam_user.md create mode 100644 scaleway/table_scaleway_iam_api_key.go create mode 100644 scaleway/table_scaleway_iam_user.go diff --git a/config/scaleway.spc b/config/scaleway.spc index 08c8e61..b7938ba 100644 --- a/config/scaleway.spc +++ b/config/scaleway.spc @@ -6,6 +6,10 @@ connection "scaleway" { # variables using the `SCW_ACCESS_KEY` and `SCW_SECRET_KEY` arguments. access_key = "YOUR_ACCESS_KEY" secret_key = "YOUR_SECRET_ACCESS_KEY" + + # Your organization ID is the identifier of your account inside Scaleway infrastructure. + # This is only required while querying the scaleway_iam_api_key and scaleway_iam_user tables. + # organization_id = "YOUR_ORGANIZATION_ID" # You may connect to one or more regions. If `regions` is not specified, # Steampipe will use a single default region using the `SCW_DEFAULT_REGION` diff --git a/docs/index.md b/docs/index.md index ebf3b82..c09733f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -71,8 +71,12 @@ connection "scaleway" { # Set the static credential with the `access_key` and `secret_key` arguments. # Alternatively, if no creds passed in config, you may set the environment # variables using the `SCW_ACCESS_KEY` and `SCW_SECRET_KEY` arguments. - # access_key = "SCWKMH185ZG5THRH7WVX" - # secret_key = "ee3b5cb8-2c81-887c-a772-17d46dd34vc7" + access_key = "SCWKMH185ZG5THRH7WVX" + secret_key = "ee3b5cb8-2c81-887c-a772-17d46dd34vc7" + + # Your organization ID is the identifier of your account inside Scaleway infrastructure. + # This is only required while querying the scaleway_iam_api_key and scaleway_iam_user tables. + # organization_id = "14czbd62-29fe-46a6-967f-5433adcb2fc5" # You may connect to one or more regions. If `regions` is not specified, # Steampipe will use a single default region using the `SCW_DEFAULT_REGION` diff --git a/docs/tables/scaleway_iam_api_key.md b/docs/tables/scaleway_iam_api_key.md new file mode 100644 index 0000000..806343d --- /dev/null +++ b/docs/tables/scaleway_iam_api_key.md @@ -0,0 +1,36 @@ +# Table: scaleway_iam_api_key + +API keys allow you to securely connect to scaleway console in your organization. + +This table requires the `organization_id` config argument to be set. + +## Examples + +### Basic info + +```sql +select + access_key, + created_at, + user_id, + expires_at, + default_project_id +from + scaleway_iam_api_key +``` + +### List API keys older than 90 days + +```sql +select + access_key, + created_at, + user_id, + expires_at, + default_project_id, + extract(day from current_timestamp - created_at) as age +from + scaleway_iam_api_key +where + extract(day from current_timestamp - created_at) > 90; +``` diff --git a/docs/tables/scaleway_iam_user.md b/docs/tables/scaleway_iam_user.md new file mode 100644 index 0000000..bdd7279 --- /dev/null +++ b/docs/tables/scaleway_iam_user.md @@ -0,0 +1,59 @@ +# Table: scaleway_iam_user + +Users allow you to connect to scaleway console in your organization. + +This table requires the `organization_id` config argument to be set. + +## Examples + +### Basic info + +```sql +select + email, + created_at, + last_login_at, + id, + status, + two_factor_enabled +from + scaleway_iam_user +``` + +### List all the users for whom MFA is not enabled + +```sql +select + email, + id, + two_factor_enabled +from + scaleway_iam_user +where + not two_factor_enabled; +``` + +### List all the users not actived + +```sql +select + email, + id, + status +from + scaleway_iam_user +where + status = 'unknown_status'; +``` + +### List all the users never connected + +```sql +select + email, + id, + last_login_at +from + scaleway_iam_user +where + last_login_at is null; diff --git a/go.sum b/go.sum index 34542b7..210d968 100644 --- a/go.sum +++ b/go.sum @@ -591,6 +591,8 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.12 h1:Aaz4T7dZp7cB2cv7D/tGtRdSMh48sRaDYr7Jh0HV4qQ= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.12/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.14 h1:yFl3jyaSVLNYXlnNYM5z2pagEk1dYQhfr1p20T1NyKY= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.14/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec= diff --git a/scaleway/connection_config.go b/scaleway/connection_config.go index a4d9196..07859ab 100644 --- a/scaleway/connection_config.go +++ b/scaleway/connection_config.go @@ -6,9 +6,10 @@ import ( ) type scalewayConfig struct { - AccessKey *string `cty:"access_key"` - SecretKey *string `cty:"secret_key"` - Regions []string `cty:"regions"` + AccessKey *string `cty:"access_key"` + SecretKey *string `cty:"secret_key"` + OrganizationID *string `cty:"organization_id"` + Regions []string `cty:"regions"` } var ConfigSchema = map[string]*schema.Attribute{ @@ -18,6 +19,9 @@ var ConfigSchema = map[string]*schema.Attribute{ "secret_key": { Type: schema.TypeString, }, + "organization_id": { + Type: schema.TypeString, + }, "regions": { Type: schema.TypeList, Elem: &schema.Attribute{Type: schema.TypeString}, diff --git a/scaleway/plugin.go b/scaleway/plugin.go index aee0e1c..5592f87 100644 --- a/scaleway/plugin.go +++ b/scaleway/plugin.go @@ -22,6 +22,8 @@ func Plugin(ctx context.Context) *plugin.Plugin { TableMap: map[string]*plugin.Table{ "scaleway_account_ssh_key": tableScalewayAccountSSHKey(ctx), "scaleway_baremetal_server": tableScalewayBaremetalServer(ctx), + "scaleway_iam_api_key": tableScalewayIamAPIKey(ctx), + "scaleway_iam_user": tableScalewayIamUser(ctx), "scaleway_instance_image": tableScalewayInstanceImage(ctx), "scaleway_instance_ip": tableScalewayInstanceIP(ctx), "scaleway_instance_security_group": tableScalewayInstanceSecurityGroup(ctx), diff --git a/scaleway/table_scaleway_iam_api_key.go b/scaleway/table_scaleway_iam_api_key.go new file mode 100644 index 0000000..d9a5824 --- /dev/null +++ b/scaleway/table_scaleway_iam_api_key.go @@ -0,0 +1,194 @@ +package scaleway + +import ( + "context" + + iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1" + + "github.com/scaleway/scaleway-sdk-go/scw" + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" +) + +//// TABLE DEFINITION + +func tableScalewayIamAPIKey(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "scaleway_iam_api_key", + Description: "API keys allow you to securely connect to scaleway console in your organization.", + List: &plugin.ListConfig{ + Hydrate: listIamAPIKeys, + KeyColumns: []*plugin.KeyColumn{ + { + Name: "access_key", + Require: plugin.Optional, + }, + }, + }, + Get: &plugin.GetConfig{ + Hydrate: getIamAPIKey, + KeyColumns: plugin.SingleColumn("access_key"), + }, + Columns: []*plugin.Column{ + { + Name: "access_key", + Description: "The access key of API key.", + Type: proto.ColumnType_STRING, + }, + { + Name: "secret_key", + Description: "The secret key of API Key.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("SecretKey").Transform(transform.ToString), + }, + { + Name: "application_id", + Description: "ID of application bearer.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("ApplicationID").Transform(transform.ToString), + }, + { + Name: "created_at", + Description: "Creation date and time of API key.", + Type: proto.ColumnType_TIMESTAMP, + }, + { + Name: "user_id", + Description: "ID of user bearer.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("UserID").Transform(transform.ToString), + }, + { + Name: "updated_at", + Description: "Last update date and time of API key.", + Type: proto.ColumnType_TIMESTAMP, + }, + { + Name: "expires_at", + Description: "The expiration date and time of API key.", + Type: proto.ColumnType_TIMESTAMP, + }, + { + Name: "default_project_id", + Description: "The default project ID specified for this API key.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("DefaultProjectID").Transform(transform.ToString), + }, + { + Name: "editable", + Description: "Whether or not the API key is editable.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "creation_ip", + Description: "The IP Address of the device which created the API key.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("CreationIP").Transform(transform.ToString), + }, + { + Name: "description", + Description: "Description of API key.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Description").Transform(transform.ToString), + }, + }, + } +} + +//// LIST FUNCTION + +func listIamAPIKeys(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create client + client, err := getSessionConfig(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("scaleway_iam_api_key.listIamAPIKeys", "connection_error", err) + return nil, err + } + + // Create SDK objects for Scaleway IAM product + iamApi := iam.NewAPI(client) + + // Get organisationID from config to request IAM API + organisationId := GetConfig(d.Connection).OrganizationID + + req := &iam.ListAPIKeysRequest{ + Page: scw.Int32Ptr(1), + OrganizationID: organisationId, + } + + // Retrieve the list of servers + maxResult := int64(100) + + // Reduce the basic request limit down if the user has only requested a small number of rows + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < maxResult { + maxResult = *limit + } + } + req.PageSize = scw.Uint32Ptr(uint32(maxResult)) + + var count int + + for { + resp, err := iamApi.ListAPIKeys(req) + if err != nil { + plugin.Logger(ctx).Error("scaleway_iam_api_key.listIamAPIKeys", "query_error", err) + } + + for _, key := range resp.APIKeys { + d.StreamListItem(ctx, key) + + // Increase the resource count by 1 + count++ + + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + + if resp.TotalCount == uint32(count) { + break + } + req.Page = scw.Int32Ptr(*req.Page + 1) + + } + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func getIamAPIKey(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create client + client, err := getSessionConfig(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("scaleway_iam_api_key.getIamAPIKey", "connection_error", err) + return nil, err + } + + // Create SDK objects for Scaleway IAM product + iamApi := iam.NewAPI(client) + + accessKey := d.EqualsQuals["access_key"].GetStringValue() + + // No inputs + if accessKey == "" { + return nil, nil + } + + data, err := iamApi.GetAPIKey(&iam.GetAPIKeyRequest{ + AccessKey: accessKey, + }) + if err != nil { + plugin.Logger(ctx).Error("scaleway_iam_api_key.getIamAPIKey", "query_error", err) + if is404Error(err) { + return nil, nil + } + return nil, err + } + + return data, nil +} diff --git a/scaleway/table_scaleway_iam_user.go b/scaleway/table_scaleway_iam_user.go new file mode 100644 index 0000000..1578b7b --- /dev/null +++ b/scaleway/table_scaleway_iam_user.go @@ -0,0 +1,189 @@ +package scaleway + +import ( + "context" + + iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1" + + "github.com/scaleway/scaleway-sdk-go/scw" + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" +) + +//// TABLE DEFINITION + +func tableScalewayIamUser(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "scaleway_iam_user", + Description: "Users allow you to connect to scaleway console in your organization.", + List: &plugin.ListConfig{ + Hydrate: listIamUsers, + }, + Get: &plugin.GetConfig{ + Hydrate: getIamUser, + KeyColumns: plugin.SingleColumn("id"), + }, + Columns: []*plugin.Column{ + { + Name: "id", + Description: "ID of user.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("ID"), + }, + { + Name: "email", + Description: "The email of user.", + Type: proto.ColumnType_STRING, + }, + { + Name: "deletable", + Description: "The deletion status of user. Owner user cannot be deleted.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "created_at", + Description: "The time when the key was created.", + Type: proto.ColumnType_TIMESTAMP, + }, + { + Name: "last_login_at", + Description: "The last login date.", + Type: proto.ColumnType_TIMESTAMP, + }, + { + Name: "updated_at", + Description: "The time when the key was last updated.", + Type: proto.ColumnType_TIMESTAMP, + }, + { + Name: "type", + Description: "The type of the user.", + Type: proto.ColumnType_JSON, + }, + { + Name: "two_factor_enabled", + Description: "The 2FA enabled.", + Type: proto.ColumnType_BOOL, + }, + { + Name: "status", + Description: "The status of invitation for the user.", + Type: proto.ColumnType_STRING, + }, + + // Scaleway standard columns + { + Name: "organization", + Description: "The ID of the organization where the server resides.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("OrganizationID"), + }, + + // Steampipe standard columns + { + Name: "title", + Description: "Title of the resource.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Email"), + }, + }, + } +} + +//// LIST FUNCTION + +func listIamUsers(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create client + client, err := getSessionConfig(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("scaleway_iam_user.listIamUsers", "connection_error", err) + return nil, err + } + + // Create SDK objects for Scaleway IAM product + iamApi := iam.NewAPI(client) + + // Get organisationID from config to request IAM API + organisationId := GetConfig(d.Connection).OrganizationID + + req := &iam.ListUsersRequest{ + Page: scw.Int32Ptr(1), + OrganizationID: organisationId, + } + + // Retrieve the list of servers + maxResult := int64(100) + + // Reduce the basic request limit down if the user has only requested a small number of rows + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < maxResult { + maxResult = *limit + } + } + req.PageSize = scw.Uint32Ptr(uint32(maxResult)) + + var count int + + for { + resp, err := iamApi.ListUsers(req) + if err != nil { + plugin.Logger(ctx).Error("scaleway_iam_user.listIamUsers", "query_error", err) + } + + for _, key := range resp.Users { + d.StreamListItem(ctx, key) + + // Increase the resource count by 1 + count++ + + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + + if resp.TotalCount == uint32(count) { + break + } + req.Page = scw.Int32Ptr(*req.Page + 1) + + } + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func getIamUser(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create client + client, err := getSessionConfig(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("scaleway_iam_user.getIamUser", "connection_error", err) + return nil, err + } + + // Create SDK objects for Scaleway IAM product + iamApi := iam.NewAPI(client) + + userId := d.EqualsQualString("id") + + // No inputs + if userId == "" { + return nil, nil + } + + data, err := iamApi.GetUser(&iam.GetUserRequest{ + UserID: userId, + }) + if err != nil { + plugin.Logger(ctx).Error("scaleway_iam_user.getIamUser", "query_error", err) + if is404Error(err) { + return nil, nil + } + return nil, err + } + + return data, nil +}