diff --git a/docs/tables/turbot_policy_value.md b/docs/tables/turbot_policy_value.md new file mode 100644 index 0000000..e275229 --- /dev/null +++ b/docs/tables/turbot_policy_value.md @@ -0,0 +1,75 @@ +# Table: turbot_policy_value + +A policy value is the effective policy setting on an instance of a resource type. Every resource that is targeted by a given policy setting will have its own value for that policy, which will be the resultant calculated policy for the "winning" policy in the hierarchy. + +Policy settings are inherited through the resource hierarchy, and values for a resource are calculated according to policy settings at or above it in the resource hierarchy. For example, a policy setting at the Turbot level will be inherited by all resources below. + +It is recommended that queries to this table should include (usually in the `where` clause) at least one +of these columns: `state`, `policy_type_id`, `resource_type_id`, `resource_type_uri` or `filter`. + +## Examples + +### List policy values by policy type ID + +```sql +select + id, + state, + is_default, + is_calculated, + policy_type_id, + type_mod_uri +from + turbot_policy_value +where + policy_type_id = 221505068398189; +``` + +### List policy values by resource ID + +```sql +select + id, + state, + is_default, + is_calculated, + resource_id, + type_mod_uri +from + turbot_policy_value +where + resource_id = 161587219904115; +``` + +### List non-default calculated policy values + +```sql +select + id, + state, + is_default, + is_calculated, + resource_type_id, + type_mod_uri +from + turbot_policy_value +where + is_calculated and not is_default; +``` + +### Filter policy values using Turbot filter syntax + +```sql +select + id, + state, + is_default, + is_calculated, + policy_type_id, + resource_id, + resource_type_id +from + turbot_policy_value +where + filter = 'state:ok'; +``` diff --git a/turbot/plugin.go b/turbot/plugin.go index 2f46606..5203350 100644 --- a/turbot/plugin.go +++ b/turbot/plugin.go @@ -28,6 +28,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "turbot_notification": tableTurbotNotification(ctx), "turbot_policy_setting": tableTurbotPolicySetting(ctx), "turbot_policy_type": tableTurbotPolicyType(ctx), + "turbot_policy_value": tableTurbotPolicyValue(ctx), "turbot_resource": tableTurbotResource(ctx), "turbot_resource_type": tableTurbotResourceType(ctx), "turbot_smart_folder": tableTurbotSmartFolder(ctx), diff --git a/turbot/table_turbot_policy_value.go b/turbot/table_turbot_policy_value.go new file mode 100644 index 0000000..970af16 --- /dev/null +++ b/turbot/table_turbot_policy_value.go @@ -0,0 +1,211 @@ +package turbot + +import ( + "context" + "fmt" + "strconv" + + "github.com/turbot/steampipe-plugin-sdk/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/plugin" + "github.com/turbot/steampipe-plugin-sdk/plugin/transform" +) + +func tableTurbotPolicyValue(ctx context.Context) *plugin.Table { + return &plugin.Table{ + Name: "turbot_policy_value", + Description: "Policy value define the value of policy known to Turbot.", + List: &plugin.ListConfig{ + Hydrate: listPolicyValue, + KeyColumns: []*plugin.KeyColumn{ + {Name: "state", Require: plugin.Optional}, + {Name: "policy_type_id", Require: plugin.Optional}, + {Name: "resource_id", Require: plugin.Optional}, + {Name: "resource_type_id", Require: plugin.Optional}, + {Name: "filter", Require: plugin.Optional}, + }, + }, + Columns: []*plugin.Column{ + // Top columns + {Name: "id", Type: proto.ColumnType_INT, Transform: transform.FromField("Turbot.ID"), Description: "Unique identifier of the policy value."}, + {Name: "policy_type_title", Type: proto.ColumnType_STRING, Transform: transform.FromField("Type.Title"), Description: "Title of the policy type."}, + {Name: "poliy_type_trunk_title", Type: proto.ColumnType_STRING, Transform: transform.FromField("Type.Trunk.Title"), Description: "Title with full path of the policy type."}, + {Name: "is_default", Type: proto.ColumnType_BOOL, Transform: transform.FromField("Default"), Description: "If true this value is derived from the default value of the type."}, + {Name: "is_calculated", Type: proto.ColumnType_BOOL, Description: "If true this value is derived from calculated setting inputs e.g. templateInput and template."}, + {Name: "precedence", Type: proto.ColumnType_STRING, Description: "Precedence of the setting: REQUIRED or RECOMMENDED."}, + {Name: "resource_id", Type: proto.ColumnType_INT, Transform: transform.FromField("Turbot.ResourceId"), Description: "ID of the resource for the policy value."}, + {Name: "resource_trunk_title", Type: proto.ColumnType_STRING, Transform: transform.FromField("Resource.Trunk.Title"), Description: "Full title (including ancestor trunk) of the resource."}, + {Name: "resource_type_id", Type: proto.ColumnType_INT, Transform: transform.FromField("Turbot.ResourceTypeID"), Description: "ID of the resource type for this policy setting."}, + {Name: "state", Type: proto.ColumnType_STRING, Description: "State of the policy value."}, + {Name: "secret_value", Type: proto.ColumnType_STRING, Transform: transform.FromField("SecretValue").Transform(convToString), Description: "Secrect value of the policy value."}, + {Name: "value", Type: proto.ColumnType_STRING, Transform: transform.FromField("Value").Transform(convToString), Description: "Value of the policy value."}, + {Name: "type_mod_uri", Type: proto.ColumnType_STRING, Transform: transform.FromField("Type.ModURI"), Description: "URI of the mod that contains the policy value."}, + + // Other columns + {Name: "filter", Type: proto.ColumnType_STRING, Transform: transform.FromQual("filter"), Description: "Filter used for this policy value list."}, + {Name: "policy_type_id", Type: proto.ColumnType_INT, Transform: transform.FromField("Turbot.PolicyTypeId"), Description: "ID of the policy type for this policy value."}, + {Name: "policy_type_default_template", Type: proto.ColumnType_STRING, Transform: transform.FromField("Type.DefaultTemplate"), Description: "Default template used to calculate template-based policy values. Should be a Jinja based YAML string."}, + {Name: "setting_id", Type: proto.ColumnType_INT, Transform: transform.FromField("Turbot.SettingId").Transform(transform.NullIfZeroValue), Description: "Policy setting Id for the policy value."}, + {Name: "dependent_controls", Type: proto.ColumnType_JSON, Description: "The controls that depends on this policy value."}, + {Name: "dependent_policy_values", Type: proto.ColumnType_JSON, Description: "The policy values that depends on this policy value."}, + {Name: "create_timestamp", Type: proto.ColumnType_TIMESTAMP, Transform: transform.FromField("Turbot.CreateTimestamp"), Description: "When the policy value was first set by Turbot. (It may have been created earlier.)"}, + {Name: "timestamp", Type: proto.ColumnType_TIMESTAMP, Transform: transform.FromField("Turbot.Timestamp"), Description: "Timestamp when the policy value was last modified (created, updated or deleted)."}, + {Name: "update_timestamp", Type: proto.ColumnType_TIMESTAMP, Transform: transform.FromField("Turbot.UpdateTimestamp"), Description: "When the policy value was last updated in Turbot."}, + {Name: "version_id", Type: proto.ColumnType_INT, Transform: transform.FromField("Turbot.VersionID"), Description: "Unique identifier for this version of the policy value."}, + {Name: "workspace", Type: proto.ColumnType_STRING, Hydrate: plugin.HydrateFunc(getTurbotWorkspace).WithCache(), Transform: transform.FromValue(), Description: "Specifies the workspace URL."}, + }, + } +} + +const ( + queryPolicyValueList = ` +query MyQuery($filter: [String!], $next_token: String) { + policyValues(filter: $filter, paging: $next_token) { + items { + default + value + state + reason + details + secretValue + isCalculated + precedence + type { + modUri + defaultTemplate + title + trunk { + title + } + } + resource { + trunk { + title + } + } + turbot { + id + policyTypeId + resourceId + resourceTypeId + settingId + createTimestamp + deleteTimestamp + timestamp + updateTimestamp + versionId + } + dependentControls { + items { + turbot { + controlTypeId + controlTypePath + controlCategoryId + controlCategoryPath + id + resourceId + resourceTypeId + } + type { + modUri + title + trunk { + title + } + } + } + } + dependentPolicyValues { + items { + type { + modUri + uri + title + trunk { + title + } + turbot { + id + title + } + } + } + } + } + paging { + next + } + } + } +` +) + +func listPolicyValue(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + conn, err := connect(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("turbot_policy_type.listPolicyType", "connection_error", err) + return nil, err + } + + filters := []string{} + quals := d.KeyColumnQuals + + filter := "" + if quals["filter"] != nil { + filter = quals["filter"].GetStringValue() + filters = append(filters, filter) + } + + // Additional filters + if quals["state"] != nil { + filters = append(filters, fmt.Sprintf("state:%s ", getQualListValues(ctx, quals, "state", "string"))) + } + + if quals["policy_type_id"] != nil { + filters = append(filters, fmt.Sprintf("policyTypeId:%s policyTypeLevel:self", getQualListValues(ctx, quals, "policy_type_id", "int64"))) + } + + if quals["resource_id"] != nil { + filters = append(filters, fmt.Sprintf("resourceId:%s resourceTypeLevel:self", getQualListValues(ctx, quals, "resource_id", "int64"))) + } + + if quals["resource_type_id"] != nil { + filters = append(filters, fmt.Sprintf("resourceTypeId:%s resourceTypeLevel:self", getQualListValues(ctx, quals, "resource_type_id", "int64"))) + } + + // Setting a high limit and page all results + var pageLimit int64 = 5000 + + // Adjust page limit, if less than default value + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < pageLimit { + pageLimit = *limit + } + } + + // Setting page limit + filters = append(filters, fmt.Sprintf("limit:%s", strconv.Itoa(int(pageLimit)))) + + nextToken := "" + for { + result := &PolicyValuesResponse{} + err = conn.DoRequest(queryPolicyValueList, map[string]interface{}{"filter": filters, "next_token": nextToken}, result) + if err != nil { + plugin.Logger(ctx).Error("turbot_policy_value.listPolicyValue", "query_error", err) + return nil, err + } + for _, r := range result.PolicyValues.Items { + d.StreamListItem(ctx, r) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.QueryStatus.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + if result.PolicyValues.Paging.Next == "" { + break + } + nextToken = result.PolicyValues.Paging.Next + } + return nil, nil +} diff --git a/turbot/types.go b/turbot/types.go index b72c27d..21b6408 100644 --- a/turbot/types.go +++ b/turbot/types.go @@ -195,6 +195,53 @@ type PolicyType struct { URI string } +type PolicyValuesResponse struct { + PolicyValues struct { + Items []PolicyValue + Paging struct { + Next string + } + } +} + +type PolicyValue struct { + Default bool + Value interface{} + State string + Reason string + Details interface{} + SecretValue interface{} + IsCalculated bool + Precedence string + Type PolicyValueType + Resource PolicyValueResourceDetails + DependentControls interface{} + DependentPolicyValues interface{} + Turbot PolicyValueTurbotProperty +} + +type PolicyValueResourceDetails struct { + Trunk struct { + Title string + } +} + +type PolicyValueType struct { + ModURI string + DefaultTemplate string + Title string + Trunk struct { + Title string + } +} + +type PolicyValueTurbotProperty struct { + TurbotResourceMetadata + PolicyTypeId string + ResourceId string + SettingId string +} + type TurbotResourceMetadata struct { ActorIdentityID string ActorPersonaID string diff --git a/turbot/utils.go b/turbot/utils.go index 8bad68f..b6d6386 100644 --- a/turbot/utils.go +++ b/turbot/utils.go @@ -87,6 +87,11 @@ func intToBool(ctx context.Context, d *transform.TransformData) (interface{}, er return v > 0, nil } +func convToString(ctx context.Context, d *transform.TransformData) (interface{}, error) { + var v interface{} = fmt.Sprint(d.Value) + return v, nil +} + func attachedResourceIDs(_ context.Context, d *transform.TransformData) (interface{}, error) { objs := d.Value.([]TurbotIDObject) ids := []int64{}