From 1c0dfe82457c014b76bbf29de442b2930ba7817b Mon Sep 17 00:00:00 2001 From: Meghana Pedduri Date: Sun, 9 Jun 2024 12:09:54 +0530 Subject: [PATCH 1/4] added resource provider for session identification response rule --- main.tf | 31 +- provider/provider.go | 10 +- ...ce_session_identification_response_rule.go | 817 ++++++++++++++++++ 3 files changed, 852 insertions(+), 6 deletions(-) create mode 100644 provider/resource_session_identification_response_rule.go diff --git a/main.tf b/main.tf index 4e44049..befe274 100644 --- a/main.tf +++ b/main.tf @@ -26,7 +26,7 @@ output "api_token" { } provider "traceable" { - platform_url="https://api-dev.traceable.ai/graphql" + platform_url="https://app-dev.traceable.ai/graphql" api_token=jsondecode(data.aws_secretsmanager_secret_version.api_token.secret_string)["api_token"] } @@ -183,4 +183,33 @@ output "agent_token" { output "agent_token_creation_timestamp" { value = data.traceable_agent_token.example.creation_timestamp +} +resource "traceable_session_identification_response_rule" "example" { + name = "example-session-rule-23" + description = "This is an example session identification rule" + environment_names = ["dev", "prod"] + service_names = ["service1", "service2"] + url_match_regexes = ["^/api/.*$", "^/internal/.*$"] + + token_extraction_condition_list { + condition_response_header { + key = "X-Example-Header" + operator = "EQUALS" + value = "example-value" + } + } + + session_token_details { + token_response_header { + token_key = "Authorization" + operator = "MATCHES_REGEX" + } + } + + obfuscation = true + expiration_type = "JWT" + + token_value_transformation_list { + json_path = "$.token" + } } \ No newline at end of file diff --git a/provider/provider.go b/provider/provider.go index f7b13a8..36cdd34 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -23,16 +23,16 @@ func Provider() *schema.Provider { "traceable_user_attribution_rule_basic_auth": resourceUserAttributionBasicAuthRule(), "traceable_user_attribution_rule_req_header": resourceUserAttributionRequestHeaderRule(), "traceable_user_attribution_rule_jwt_authentication": resourceUserAttributionJwtAuthRule(), - "traceable_user_attribution_rule_response_body": resourceUserAttributionResponseBodyRule(), - "traceable_user_attribution_rule_custom_json": resourceUserAttributionCustomJsonRule(), - "traceable_user_attribution_rule_custom_token": resourceUserAttributionCustomTokenRule(), - "traceable_notification_channel": resourceNotificationChannelRule(), + "traceable_user_attribution_rule_response_body": resourceUserAttributionResponseBodyRule(), + "traceable_user_attribution_rule_custom_json": resourceUserAttributionCustomJsonRule(), + "traceable_user_attribution_rule_custom_token": resourceUserAttributionCustomTokenRule(), + "traceable_notification_channel": resourceNotificationChannelRule(), "traceable_notification_rule_logged_threat_activity": resourceNotificationRuleLoggedThreatActivity(), "traceable_api_naming_rule": resourceApiNamingRule(), "traceable_api_exclusion_rule": resourceApiExclusionRule(), "traceable_label_creation_rule": resourceLabelCreationRule(), "traceable_agent_token": resourceAgentToken(), - + "traceable_session_identification_response_rule": resourceSessionIdentificationResponseRule(), }, DataSourcesMap: map[string]*schema.Resource{ "traceable_notification_channels": dataSourceNotificationChannel(), diff --git a/provider/resource_session_identification_response_rule.go b/provider/resource_session_identification_response_rule.go new file mode 100644 index 0000000..6c91207 --- /dev/null +++ b/provider/resource_session_identification_response_rule.go @@ -0,0 +1,817 @@ +package provider + +import ( + "encoding/json" + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceSessionIdentificationResponseRule() *schema.Resource { + return &schema.Resource{ + Create: resourceSessionIdentificationResponseRuleCreate, + Read: resourceSessionIdentificationResponseRuleRead, + Update: resourceSessionIdentificationResponseRuleUpdate, + Delete: resourceSessionIdentificationResponseRuleDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "The name of the Session Identification Rule", + Required: true, + }, + "description": { + Type: schema.TypeString, + Description: "The description of the Session Identification Rule", + Optional: true, + }, + "environment_names": { + Type: schema.TypeList, + Description: "List of environment names", + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "service_names": { + Type: schema.TypeList, + Description: "List of service names", + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "url_match_regexes": { + Type: schema.TypeList, + Description: "List of URL match regexes", + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "token_extraction_condition_list": { + Type: schema.TypeList, + Description: "Conditions to satisfy for extracting Session Token", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "condition_response_header": { + Type: schema.TypeSet, + Description: "Attribute type response header", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Description: "response header key", + Required: true, + }, + "operator": { + Type: schema.TypeString, + Description: "match operator", + Required: true, + }, + "value": { + Type: schema.TypeString, + Description: "response header value", + Required: true, + }, + }, + }, + DiffSuppressFunc: suppressConditionListDiff, + }, + "condition_response_cookie": { + Type: schema.TypeSet, + Description: "Attribute type response cookie", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Description: "response cookie key", + Required: true, + }, + "operator": { + Type: schema.TypeString, + Description: "match operator", + Required: true, + }, + "value": { + Type: schema.TypeString, + Description: "response cookie value", + Required: true, + }, + }, + }, + DiffSuppressFunc: suppressConditionListDiff, + }, + }, + }, + }, + "session_token_details": { + Type: schema.TypeList, + Description: "Details of the session token of type response", + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "token_response_header": { + Type: schema.TypeSet, + Description: "response header for token", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "token_key": { + Type: schema.TypeString, + Description: "Test header key", + Required: true, + }, + "operator": { + Type: schema.TypeString, + Description: "match operator", + Required: true, + }, + }, + }, + DiffSuppressFunc: suppressSessionTokenDetailsDiff, + }, + "token_response_cookie": { + Type: schema.TypeSet, + Description: "response cookie for token", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "token_key": { + Type: schema.TypeString, + Description: "Test cookie key", + Required: true, + }, + "operator": { + Type: schema.TypeString, + Description: "match operator", + Required: true, + }, + }, + }, + DiffSuppressFunc: suppressSessionTokenDetailsDiff, + }, + "token_response_body": { + Type: schema.TypeBool, + Description: "response body for token", + Optional: true, + DiffSuppressFunc: suppressSessionTokenDetailsDiff, + }, + }, + }, + }, + "obfuscation": { + Type: schema.TypeBool, + Description: "If the obfuscation strategy of HASH to be used", + Required: true, + }, + "expiration_type": { + Type: schema.TypeString, + Description: "expiration is jwt based or not applicable", + Optional: true, + }, + "token_value_transformation_list": { + Type: schema.TypeList, + Description: "Conditions to satisfy for extracting Session Token", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "json_path": { + Type: schema.TypeString, + Description: "the json path group for value transformation", + Optional: true, + DiffSuppressFunc: suppressValueTransformationListDiff, + }, + "regex_capture_group": { + Type: schema.TypeString, + Description: "the regex capture group for value transformation", + Optional: true, + DiffSuppressFunc: suppressValueTransformationListDiff, + }, + "jwt_payload_claim": { + Type: schema.TypeString, + Description: "the jwt payload claim for value transformation", + Optional: true, + DiffSuppressFunc: suppressValueTransformationListDiff, + }, + "base64": { + Type: schema.TypeBool, + Description: "whether we use the base64 value transformation", + Optional: true, + DiffSuppressFunc: suppressValueTransformationListDiff, + }, + }, + }, + }, + }, + } +} + +func suppressConditionListDiff(k, old, new string, d *schema.ResourceData) bool { + return suppressListDiff(old, new) +} + +func suppressSessionTokenDetailsDiff(k, old, new string, d *schema.ResourceData) bool { + return suppressListDiff(old, new) +} + +func suppressValueTransformationListDiff(k, old, new string, d *schema.ResourceData) bool { + return suppressListDiff(old, new) +} + +func suppressListDiff(old, new string) bool { + oldList := strings.FieldsFunc(old, func(r rune) bool { return r == ',' || r == '[' || r == ']' || r == '{' || r == '}' || r == '"' }) + newList := strings.FieldsFunc(new, func(r rune) bool { return r == ',' || r == '[' || r == ']' || r == '{' || r == '}' || r == '"' }) + if len(oldList) != len(newList) { + return false + } + oldMap := make(map[string]bool) + for _, v := range oldList { + oldMap[v] = true + } + for _, v := range newList { + if !oldMap[v] { + return false + } + } + return true +} + +func resourceSessionIdentificationResponseRuleCreate(d *schema.ResourceData, meta interface{}) error { + name := d.Get("name").(string) + + descriptionStr := "" + if v, ok := d.GetOk("description"); ok { + descriptionStr = fmt.Sprintf(`description: "%s",`, v.(string)) + } + + scopeStr := "" + envNames := d.Get("environment_names").([]interface{}) + serviceNames := d.Get("service_names").([]interface{}) + urlMatchRegexes := d.Get("url_match_regexes").([]interface{}) + + envNamesStr := buildStringArray(interfaceSliceToStringSlice(envNames)) + serviceNamesStr := buildStringArray(interfaceSliceToStringSlice(serviceNames)) + urlMatchRegexesStr := buildStringArray(interfaceSliceToStringSlice(urlMatchRegexes)) + + if len(envNames) > 0 { + scopeStr = fmt.Sprintf(`scope: { + environmentNames: %s`, envNamesStr) + if len(serviceNames) > 0 { + scopeStr += fmt.Sprintf(`, serviceNames: %s`, serviceNamesStr) + } + if len(urlMatchRegexes) > 0 { + scopeStr += fmt.Sprintf(`, urlMatchRegexes: %s`, urlMatchRegexesStr) + } + scopeStr += "}," + } else if len(serviceNames) > 0 && (len(serviceNames) != 1 || serviceNames[0] != "") { + scopeStr = fmt.Sprintf(`scope: { + serviceNames: %s + },`, serviceNamesStr) + } + + conditionListStr := "" + if v, ok := d.GetOk("token_extraction_condition_list"); ok { + conditions := v.([]interface{}) + if len(conditions) > 0 { + conditionListStr = `predicate: { predicateType: LOGICAL, logicalPredicate: { operator: AND, children: [` + + for _, condition := range conditions { + conditionMap := condition.(map[string]interface{}) + + if v, ok := conditionMap["condition_response_header"]; ok { + conditionListStr += buildConditionList("HEADER", v.(*schema.Set).List()) + } + if v, ok := conditionMap["condition_response_cookie"]; ok { + conditionListStr += buildConditionList("COOKIE", v.(*schema.Set).List()) + } + } + + conditionListStr += `]}},` + } + } + + // the trailing comma is removed if no conditions are present + if conditionListStr == `predicate: { predicateType: LOGICAL, logicalPredicate: { operator: AND, children: []}}, ` { + conditionListStr = "" + } + + expirationTypeStr := "" + if v, ok := d.GetOk("expiration_type"); ok { + expirationTypeStr = fmt.Sprintf(`expirationType: %s,`, v.(string)) + } + + obfuscationStr := "" + if v, ok := d.GetOk("obfuscation"); ok { + if v.(bool) { + obfuscationStr = `obfuscationStrategy: HASH,` + } + } + + responseAttributeKeyLocationStr := "" + if v, ok := d.GetOk("session_token_details"); ok { + sessionTokenDetails := v.([]interface{})[0].(map[string]interface{}) + + if tokenresponseHeader, ok := sessionTokenDetails["token_response_header"]; ok { + if len(tokenresponseHeader.(*schema.Set).List()) > 0 { + responseAttributeKeyLocationStr = "HEADER" + } + } else if tokenresponseCookie, ok := sessionTokenDetails["token_response_cookie"]; ok { + if len(tokenresponseCookie.(*schema.Set).List()) > 0 { + responseAttributeKeyLocationStr = "COOKIE" + } + } else if tokenresponseBody, ok := sessionTokenDetails["token_response_body"].(bool); ok && tokenresponseBody { + responseAttributeKeyLocationStr = "BODY" + } + } + + tokenMatchConditionStr := "" + if responseAttributeKeyLocationStr != "BODY" && responseAttributeKeyLocationStr != "" { + if v, ok := d.GetOk("session_token_details"); ok { + sessionTokenDetails := v.([]interface{})[0].(map[string]interface{}) + if tokenresponseHeader, ok := sessionTokenDetails["token_response_header"].([]interface{}); ok && len(tokenresponseHeader) > 0 { + tokenKey := tokenresponseHeader[0].(map[string]interface{})["token_key"].(string) + operator := tokenresponseHeader[0].(map[string]interface{})["operator"].(string) + tokenMatchConditionStr = fmt.Sprintf(`matchCondition: { matchOperator: %s, stringValue: "%s" },`, operator, tokenKey) + } else if tokenresponseCookie, ok := sessionTokenDetails["token_response_cookie"].([]interface{}); ok && len(tokenresponseCookie) > 0 { + tokenKey := tokenresponseCookie[0].(map[string]interface{})["token_key"].(string) + operator := tokenresponseCookie[0].(map[string]interface{})["operator"].(string) + tokenMatchConditionStr = fmt.Sprintf(`matchCondition: { matchOperator: %s, stringValue: "%s" },`, operator, tokenKey) + } + } + } + + valueProjectionsStr := "" + if v, ok := d.GetOk("token_value_transformation_list"); ok { + valueTransformations := v.([]interface{}) + var valueProjections []string + for _, transformation := range valueTransformations { + transformationMap := transformation.(map[string]interface{}) + if jsonPath, ok := transformationMap["json_path"].(string); ok && jsonPath != "" { + valueProjections = append(valueProjections, fmt.Sprintf(`{ + valueProjectionType: JSON_PATH, + jsonPathProjection: { path: "%s" } + }`, jsonPath)) + } + if regexCaptureGroup, ok := transformationMap["regex_capture_group"].(string); ok && regexCaptureGroup != "" { + valueProjections = append(valueProjections, fmt.Sprintf(`{ + valueProjectionType: REGEX_CAPTURE_GROUP, + regexCaptureGroupProjection: { regexCaptureGroup: "%s" } + }`, regexCaptureGroup)) + } + if jwtPayloadClaim, ok := transformationMap["jwt_payload_claim"].(string); ok && jwtPayloadClaim != "" { + valueProjections = append(valueProjections, fmt.Sprintf(`{ + valueProjectionType: JWT_PAYLOAD_CLAIM, + jwtPayloadClaimProjection: { claim: "%s" } + }`, jwtPayloadClaim)) + } + if base64, ok := transformationMap["base64"].(bool); ok && base64 { + valueProjections = append(valueProjections, `{ valueProjectionType: BASE64 }`) + } + } + if len(valueProjections) > 0 { + valueProjectionsStr = fmt.Sprintf("valueProjections: [%s]", strings.Join(valueProjections, ", ")) + } + } + + query := fmt.Sprintf(`mutation { + createSessionIdentificationRuleV2( + create: { + name: "%s" + %s + %s + sessionTokenRules: [ + { + %s + tokenType: RESPONSE + responseSessionTokenDetails: { + responseAttributeKeyLocation: %s + %s + } + sessionTokenValueRule: { + %s + projectionRoot: { + projectionType: ATTRIBUTE + attributeProjection: { + %s + %s + } + } + } + } + ] + } + ) { + id + } + } + `, name, descriptionStr, scopeStr, conditionListStr, responseAttributeKeyLocationStr, expirationTypeStr, obfuscationStr, tokenMatchConditionStr, valueProjectionsStr) + + var response map[string]interface{} + log.Printf(query) + responseStr, err := executeQuery(query, meta) + err = json.Unmarshal([]byte(responseStr), &response) + if err != nil { + return fmt.Errorf("Error while executing GraphQL query: %s", err) + + } + + log.Printf("GraphQL response: %s", responseStr) + + if response["data"] != nil && response["data"].(map[string]interface{})["createSessionIdentificationRuleV2"] != nil { + id := response["data"].(map[string]interface{})["createSessionIdentificationRuleV2"].(map[string]interface{})["id"].(string) + d.SetId(id) + } else { + return fmt.Errorf("could not create Session Identification response rule, no ID returned") + } + + return nil + +} + +func buildStringArray(input []string) string { + if len(input) == 0 { + return "[]" + } + output := "[" + for _, v := range input { + output += fmt.Sprintf(`"%s",`, v) + } + output = output[:len(output)-1] // Remove trailing comma + output += "]" + return output +} + +func interfaceSliceToStringSlice(input []interface{}) []string { + var output []string + for _, v := range input { + output = append(output, v.(string)) + } + return output +} + +func buildConditionList(attributeType string, conditions []interface{}) string { + conditionStr := "" + for _, condition := range conditions { + conditionMap := condition.(map[string]interface{}) + key := conditionMap["key"].(string) + operator := conditionMap["operator"].(string) + value := conditionMap["value"].(string) + conditionStr += fmt.Sprintf(`{ + predicateType: ATTRIBUTE, + attributePredicate: { + attributeProjection: { + matchCondition: { + matchOperator: %s, + stringValue: "%s" + } + }, + matchCondition: { + matchOperator: %s, + stringValue: "%s" + }, + attributeKeyLocationType: RESPONSE, + responseAttributeKeyLocation: %s + } + },`, operator, key, operator, value, attributeType) + } + // Remove trailing comma + if len(conditionStr) > 0 { + conditionStr = conditionStr[:len(conditionStr)-1] + } + return conditionStr +} + +func resourceSessionIdentificationResponseRuleRead(d *schema.ResourceData, meta interface{}) error { + readQuery := `{sessionIdentificationRulesV2{count results{id scope{environmentNames serviceNames urlMatchRegexes}description name sessionTokenRules{predicate{attributePredicate{attributeKeyLocationType attributeProjection{matchCondition{matchOperator stringValue}valueProjections{jsonPathProjection{path}jwtPayloadClaimProjection{claim}regexCaptureGroupProjection{regexCaptureGroup}valueProjectionType}}matchCondition{matchOperator stringValue}responseAttributeKeyLocation responseAttributeKeyLocation}customProjection{customJson}logicalPredicate{children{attributePredicate{attributeKeyLocationType attributeProjection{matchCondition{matchOperator stringValue}valueProjections{jsonPathProjection{path}jwtPayloadClaimProjection{claim}regexCaptureGroupProjection{regexCaptureGroup}valueProjectionType}}matchCondition{matchOperator stringValue}responseAttributeKeyLocation responseAttributeKeyLocation}predicateType}operator}predicateType}responseSessionTokenDetails{responseAttributeKeyLocation expirationType}responseSessionTokenDetails{attributeExpiration{expirationFormat projectionRoot{attributeProjection{matchCondition{matchOperator stringValue}valueProjections{jsonPathProjection{path}jwtPayloadClaimProjection{claim}regexCaptureGroupProjection{regexCaptureGroup}valueProjectionType}}customProjection{customJson}projectionType}responseAttributeKeyLocation}expirationType responseAttributeKeyLocation}sessionTokenValueRule{obfuscationStrategy projectionRoot{attributeProjection{matchCondition{matchOperator stringValue}valueProjections{jsonPathProjection{path}jwtPayloadClaimProjection{claim}regexCaptureGroupProjection{regexCaptureGroup}valueProjectionType}}customProjection{customJson}projectionType}}tokenType}status{disabled}}total}}` + + var response map[string]interface{} + responseStr, err := executeQuery(readQuery, meta) + if err != nil { + return fmt.Errorf("Error: %s", err) + } + log.Printf("This is the GraphQL query: %s", readQuery) + log.Printf("This is the GraphQL response: %s", responseStr) + err = json.Unmarshal([]byte(responseStr), &response) + if err != nil { + return fmt.Errorf("Error: %s", err) + } + + id := d.Id() + ruleDetails := getRuleDetailsFromRulesListUsingIdName(response, "sessionIdentificationRulesV2", id, "id", "name") + log.Printf("Session Identification Rule: %s", ruleDetails) + + d.Set("name", ruleDetails["name"]) + d.Set("description", ruleDetails["description"]) + + scope := ruleDetails["scope"].(map[string]interface{}) + if environmentNames, ok := scope["environmentNames"]; ok { + d.Set("environment_names", environmentNames) + } + if serviceNames, ok := scope["serviceNames"]; ok { + d.Set("service_names", serviceNames) + } + if urlMatchRegexes, ok := scope["urlMatchRegexes"]; ok { + d.Set("url_match_regexes", urlMatchRegexes) + } + + sessionTokenRules := ruleDetails["sessionTokenRules"].([]interface{}) + if len(sessionTokenRules) > 0 { + sessionTokenRule := sessionTokenRules[0].(map[string]interface{}) + if predicate, ok := sessionTokenRule["predicate"].(map[string]interface{}); ok { + if logicalPredicate, ok := predicate["logicalPredicate"].(map[string]interface{}); ok { + if children, ok := logicalPredicate["children"].([]interface{}); ok { + for _, child := range children { + childMap := child.(map[string]interface{}) + if attributePredicate, ok := childMap["attributePredicate"].(map[string]interface{}); ok { + if attributeProjection, ok := attributePredicate["attributeProjection"].(map[string]interface{}); ok { + if matchCondition, ok := attributeProjection["matchCondition"].(map[string]interface{}); ok { + d.Set("condition_response_header", map[string]interface{}{ + "key": matchCondition["stringValue"].(string), + "operator": matchCondition["matchOperator"].(string), + }) + } + } + } + } + } + } + } + + if responseSessionTokenDetails, ok := sessionTokenRule["responseSessionTokenDetails"].(map[string]interface{}); ok { + responseAttributeKeyLocation := responseSessionTokenDetails["responseAttributeKeyLocation"].(string) + d.Set("session_token_details", map[string]interface{}{ + "token_response_header": map[string]interface{}{ + "responseAttributeKeyLocation": responseAttributeKeyLocation, + }, + }) + } + + if sessionTokenValueRule, ok := sessionTokenRule["sessionTokenValueRule"].(map[string]interface{}); ok { + obfuscationStrategy := sessionTokenValueRule["obfuscationStrategy"].(string) + if obfuscationStrategy == "HASH" { + d.Set("obfuscation", true) + } else { + d.Set("obfuscation", false) + } + + if projectionRoot, ok := sessionTokenValueRule["projectionRoot"].(map[string]interface{}); ok { + if attributeProjection, ok := projectionRoot["attributeProjection"].(map[string]interface{}); ok { + if valueProjections, ok := attributeProjection["valueProjections"].([]interface{}); ok { + var transformations []interface{} + for _, valueProjection := range valueProjections { + valueProjectionMap := valueProjection.(map[string]interface{}) + transformation := make(map[string]interface{}) + switch valueProjectionMap["valueProjectionType"].(string) { + case "JSON_PATH": + transformation["json_path"] = valueProjectionMap["jsonPathProjection"].(map[string]interface{})["path"].(string) + case "REGEX_CAPTURE_GROUP": + transformation["regex_capture_group"] = valueProjectionMap["regexCaptureGroupProjection"].(map[string]interface{})["regexCaptureGroup"].(string) + case "JWT_PAYLOAD_CLAIM": + transformation["jwt_payload_claim"] = valueProjectionMap["jwtPayloadClaimProjection"].(map[string]interface{})["claim"].(string) + case "BASE64": + transformation["base64"] = true + } + transformations = append(transformations, transformation) + } + d.Set("token_value_transformation_list", transformations) + } + } + } + } + } + + return nil +} + +func resourceSessionIdentificationResponseRuleUpdate(d *schema.ResourceData, meta interface{}) error { + id := d.Id() + name := d.Get("name").(string) + + descriptionStr := "" + if v, ok := d.GetOk("description"); ok { + descriptionStr = fmt.Sprintf(`description: "%s",`, v.(string)) + } + + scopeStr := "" + envNames := d.Get("environment_names").([]interface{}) + serviceNames := d.Get("service_names").([]interface{}) + urlMatchRegexes := d.Get("url_match_regexes").([]interface{}) + + envNamesStr := buildStringArray(interfaceSliceToStringSlice(envNames)) + serviceNamesStr := buildStringArray(interfaceSliceToStringSlice(serviceNames)) + urlMatchRegexesStr := buildStringArray(interfaceSliceToStringSlice(urlMatchRegexes)) + + if len(envNames) > 0 && (len(envNames) != 1 || envNames[0] != "") { + scopeStr = fmt.Sprintf(`scope: { + environmentNames: %s`, envNamesStr) + if len(serviceNames) > 0 { + scopeStr += fmt.Sprintf(`, serviceNames: %s`, serviceNamesStr) + } + if len(urlMatchRegexes) > 0 { + scopeStr += fmt.Sprintf(`, urlMatchRegexes: %s`, urlMatchRegexesStr) + } + scopeStr += "}," + } else if len(serviceNames) > 0 && (len(serviceNames) != 1 || serviceNames[0] != "") { + scopeStr = fmt.Sprintf(`scope: { + serviceNames: %s + },`, serviceNamesStr) + } + + conditionListStr := "" + if v, ok := d.GetOk("token_extraction_condition_list"); ok { + conditions := v.([]interface{}) + if len(conditions) > 0 { + conditionListStr = `predicate: { predicateType: LOGICAL, logicalPredicate: { operator: AND, children: [` + + for _, condition := range conditions { + conditionMap := condition.(map[string]interface{}) + + if v, ok := conditionMap["condition_response_header"]; ok { + conditionListStr += buildConditionList("HEADER", v.(*schema.Set).List()) + } + if v, ok := conditionMap["condition_response_cookie"]; ok { + conditionListStr += buildConditionList("COOKIE", v.(*schema.Set).List()) + } + } + + conditionListStr += `]}},` + } + } + + if conditionListStr == `predicate: { predicateType: LOGICAL, logicalPredicate: { operator: AND, children: []}},` { + conditionListStr = "" + } + + expirationTypeStr := "" + if v, ok := d.GetOk("expiration_type"); ok { + expirationTypeStr = fmt.Sprintf(`expirationType: %s,`, v.(string)) + } + + obfuscationStr := "" + if v, ok := d.GetOk("obfuscation"); ok { + if v.(bool) { + obfuscationStr = `obfuscationStrategy: HASH,` + } + } + + responseAttributeKeyLocationStr := "" + if v, ok := d.GetOk("session_token_details"); ok { + sessionTokenDetails := v.([]interface{})[0].(map[string]interface{}) + + if tokenresponseHeader, ok := sessionTokenDetails["token_response_header"]; ok { + if len(tokenresponseHeader.(*schema.Set).List()) > 0 { + responseAttributeKeyLocationStr = "HEADER" + } + } else if tokenresponseCookie, ok := sessionTokenDetails["token_response_cookie"]; ok { + if len(tokenresponseCookie.(*schema.Set).List()) > 0 { + responseAttributeKeyLocationStr = "COOKIE" + } + } else if tokenresponseBody, ok := sessionTokenDetails["token_response_body"].(bool); ok && tokenresponseBody { + responseAttributeKeyLocationStr = "BODY" + } + } + + tokenMatchConditionStr := "" + if responseAttributeKeyLocationStr != "BODY" && responseAttributeKeyLocationStr != "" { + if v, ok := d.GetOk("session_token_details"); ok { + sessionTokenDetails := v.([]interface{})[0].(map[string]interface{}) + + if tokenresponseHeader, ok := sessionTokenDetails["token_response_header"].([]interface{}); ok && len(tokenresponseHeader) > 0 { + tokenKey := tokenresponseHeader[0].(map[string]interface{})["token_key"].(string) + operator := tokenresponseHeader[0].(map[string]interface{})["operator"].(string) + tokenMatchConditionStr = fmt.Sprintf(`matchCondition: { matchOperator: %s, stringValue: "%s" },`, operator, tokenKey) + } else if tokenresponseCookie, ok := sessionTokenDetails["token_response_cookie"].([]interface{}); ok && len(tokenresponseCookie) > 0 { + tokenKey := tokenresponseCookie[0].(map[string]interface{})["token_key"].(string) + operator := tokenresponseCookie[0].(map[string]interface{})["operator"].(string) + tokenMatchConditionStr = fmt.Sprintf(`matchCondition: { matchOperator: %s, stringValue: "%s" },`, operator, tokenKey) + } + } + } + + valueProjectionsStr := "" + if v, ok := d.GetOk("token_value_transformation_list"); ok { + valueTransformations := v.([]interface{}) + var valueProjections []string + + for _, transformation := range valueTransformations { + transformationMap := transformation.(map[string]interface{}) + + if jsonPath, ok := transformationMap["json_path"].(string); ok && jsonPath != "" { + valueProjections = append(valueProjections, fmt.Sprintf(`{ + valueProjectionType: JSON_PATH, + jsonPathProjection: { path: "%s" } + }`, jsonPath)) + } + + if regexCaptureGroup, ok := transformationMap["regex_capture_group"].(string); ok && regexCaptureGroup != "" { + valueProjections = append(valueProjections, fmt.Sprintf(`{ + valueProjectionType: REGEX_CAPTURE_GROUP, + regexCaptureGroupProjection: { regexCaptureGroup: "%s" } + }`, regexCaptureGroup)) + } + + if jwtPayloadClaim, ok := transformationMap["jwt_payload_claim"].(string); ok && jwtPayloadClaim != "" { + valueProjections = append(valueProjections, fmt.Sprintf(`{ + valueProjectionType: JWT_PAYLOAD_CLAIM, + jwtPayloadClaimProjection: { claim: "%s" } + }`, jwtPayloadClaim)) + } + + if base64, ok := transformationMap["base64"].(bool); ok && base64 { + valueProjections = append(valueProjections, `{ valueProjectionType: BASE64 }`) + } + } + + if len(valueProjections) > 0 { + valueProjectionsStr = fmt.Sprintf("valueProjections: [%s]", strings.Join(valueProjections, ", ")) + } + } + + query := fmt.Sprintf(`mutation { + updateSessionIdentificationRuleV2( + update: { + id: "%s" + name: "%s" + %s + %s + sessionTokenRules: [ + { + %s + tokenType: RESPONSE + responseSessionTokenDetails: { + responseAttributeKeyLocation: %s + %s + } + sessionTokenValueRule: { + %s + projectionRoot: { + projectionType: ATTRIBUTE + attributeProjection: { + %s + %s + } + } + } + } + ] + } + ) { + id + } + } + `, id, name, descriptionStr, scopeStr, conditionListStr, responseAttributeKeyLocationStr, expirationTypeStr, obfuscationStr, tokenMatchConditionStr, valueProjectionsStr) + + var response map[string]interface{} + responseStr, err := executeQuery(query, meta) + if err != nil { + return fmt.Errorf("Error while executing GraphQL query: %s", err) + } + err = json.Unmarshal([]byte(responseStr), &response) + if err != nil { + return fmt.Errorf("Error while unmarshalling GraphQL response: %s", err) + } + + log.Printf("GraphQL response: %s", responseStr) + + if response["data"] != nil && response["data"].(map[string]interface{})["updateSessionIdentificationRuleV2"] != nil { + d.SetId(id) + } else { + return fmt.Errorf("could not update Session Identification response rule, no ID returned") + } + + return nil +} + +func resourceSessionIdentificationResponseRuleDelete(d *schema.ResourceData, meta interface{}) error { + id := d.Id() + + query := fmt.Sprintf(`mutation { + deleteSessionIdentificationRuleV2( + delete: { id: "%s" } + ) { + success + } + } + `, id) + + var response map[string]interface{} + responseStr, err := executeQuery(query, meta) + if err != nil { + return fmt.Errorf("Error while executing GraphQL query: %s", err) + } + err = json.Unmarshal([]byte(responseStr), &response) + if err != nil { + return fmt.Errorf("Error while unmarshalling GraphQL response: %s", err) + } + + log.Printf("GraphQL response: %s", responseStr) + + success, ok := response["data"].(map[string]interface{})["deleteSessionIdentificationRuleV2"].(map[string]interface{})["success"].(bool) + if !ok || !success { + return fmt.Errorf("failed to delete Session Identification response rule") + } + + d.SetId("") + return nil +} From f17a8bf1ba530109da77f1a6b1d34060611c2b88 Mon Sep 17 00:00:00 2001 From: Meghana Pedduri Date: Thu, 20 Jun 2024 13:33:43 +0530 Subject: [PATCH 2/4] fixed delete issue --- provider/resource_session_identification_response_rule.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/provider/resource_session_identification_response_rule.go b/provider/resource_session_identification_response_rule.go index 6c91207..41d8702 100644 --- a/provider/resource_session_identification_response_rule.go +++ b/provider/resource_session_identification_response_rule.go @@ -499,6 +499,10 @@ func resourceSessionIdentificationResponseRuleRead(d *schema.ResourceData, meta id := d.Id() ruleDetails := getRuleDetailsFromRulesListUsingIdName(response, "sessionIdentificationRulesV2", id, "id", "name") + if len(ruleDetails) == 0 { + d.SetId("") + return nil + } log.Printf("Session Identification Rule: %s", ruleDetails) d.Set("name", ruleDetails["name"]) From f6406306df2d674a34d2ccfbdd08ccf7dcb77d16 Mon Sep 17 00:00:00 2001 From: Meghana Pedduri Date: Fri, 21 Jun 2024 14:52:58 +0530 Subject: [PATCH 3/4] final changes --- main.tf | 6 ++-- ...ce_session_identification_response_rule.go | 35 ++++++++----------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/main.tf b/main.tf index befe274..3bb0e3d 100644 --- a/main.tf +++ b/main.tf @@ -187,8 +187,8 @@ output "agent_token_creation_timestamp" { resource "traceable_session_identification_response_rule" "example" { name = "example-session-rule-23" description = "This is an example session identification rule" - environment_names = ["dev", "prod"] - service_names = ["service1", "service2"] + environment_names = ["ast_perf", "debug"] + service_names = [] url_match_regexes = ["^/api/.*$", "^/internal/.*$"] token_extraction_condition_list { @@ -201,7 +201,7 @@ resource "traceable_session_identification_response_rule" "example" { session_token_details { token_response_header { - token_key = "Authorization" + token_key = "Author" operator = "MATCHES_REGEX" } } diff --git a/provider/resource_session_identification_response_rule.go b/provider/resource_session_identification_response_rule.go index 41d8702..ec48b70 100644 --- a/provider/resource_session_identification_response_rule.go +++ b/provider/resource_session_identification_response_rule.go @@ -309,35 +309,28 @@ func resourceSessionIdentificationResponseRuleCreate(d *schema.ResourceData, met } responseAttributeKeyLocationStr := "" + tokenMatchConditionStr := "" if v, ok := d.GetOk("session_token_details"); ok { sessionTokenDetails := v.([]interface{})[0].(map[string]interface{}) - if tokenresponseHeader, ok := sessionTokenDetails["token_response_header"]; ok { - if len(tokenresponseHeader.(*schema.Set).List()) > 0 { + if tokenResponseHeader, ok := sessionTokenDetails["token_response_header"]; ok { + if len(tokenResponseHeader.(*schema.Set).List()) > 0 { responseAttributeKeyLocationStr = "HEADER" + tokenResponseHeaderList := tokenResponseHeader.(*schema.Set).List() + tokenKey := tokenResponseHeaderList[0].(map[string]interface{})["token_key"].(string) + operator := tokenResponseHeaderList[0].(map[string]interface{})["operator"].(string) + tokenMatchConditionStr = fmt.Sprintf(`matchCondition: { matchOperator: %s, stringValue: "%s" },`, operator, tokenKey) } - } else if tokenresponseCookie, ok := sessionTokenDetails["token_response_cookie"]; ok { - if len(tokenresponseCookie.(*schema.Set).List()) > 0 { + } else if tokenResponseCookie, ok := sessionTokenDetails["token_response_cookie"]; ok { + if len(tokenResponseCookie.(*schema.Set).List()) > 0 { responseAttributeKeyLocationStr = "COOKIE" - } - } else if tokenresponseBody, ok := sessionTokenDetails["token_response_body"].(bool); ok && tokenresponseBody { - responseAttributeKeyLocationStr = "BODY" - } - } - - tokenMatchConditionStr := "" - if responseAttributeKeyLocationStr != "BODY" && responseAttributeKeyLocationStr != "" { - if v, ok := d.GetOk("session_token_details"); ok { - sessionTokenDetails := v.([]interface{})[0].(map[string]interface{}) - if tokenresponseHeader, ok := sessionTokenDetails["token_response_header"].([]interface{}); ok && len(tokenresponseHeader) > 0 { - tokenKey := tokenresponseHeader[0].(map[string]interface{})["token_key"].(string) - operator := tokenresponseHeader[0].(map[string]interface{})["operator"].(string) - tokenMatchConditionStr = fmt.Sprintf(`matchCondition: { matchOperator: %s, stringValue: "%s" },`, operator, tokenKey) - } else if tokenresponseCookie, ok := sessionTokenDetails["token_response_cookie"].([]interface{}); ok && len(tokenresponseCookie) > 0 { - tokenKey := tokenresponseCookie[0].(map[string]interface{})["token_key"].(string) - operator := tokenresponseCookie[0].(map[string]interface{})["operator"].(string) + tokenResponseCookieList := tokenResponseCookie.(*schema.Set).List() + tokenKey := tokenResponseCookieList[0].(map[string]interface{})["token_key"].(string) + operator := tokenResponseCookieList[0].(map[string]interface{})["operator"].(string) tokenMatchConditionStr = fmt.Sprintf(`matchCondition: { matchOperator: %s, stringValue: "%s" },`, operator, tokenKey) } + } else if tokenResponseBody, ok := sessionTokenDetails["token_response_body"].(bool); ok && tokenResponseBody { + responseAttributeKeyLocationStr = "BODY" } } From a875c96e0406c474e9fa99c022d3c7785b967d8c Mon Sep 17 00:00:00 2001 From: Meghana Pedduri Date: Wed, 26 Jun 2024 02:17:58 +0530 Subject: [PATCH 4/4] made final changes --- main.tf | 19 +- ...ce_session_identification_response_rule.go | 399 ++++++++++-------- 2 files changed, 240 insertions(+), 178 deletions(-) diff --git a/main.tf b/main.tf index 3bb0e3d..7ce4216 100644 --- a/main.tf +++ b/main.tf @@ -26,7 +26,7 @@ output "api_token" { } provider "traceable" { - platform_url="https://app-dev.traceable.ai/graphql" + platform_url="https://api-dev.traceable.ai/graphql" api_token=jsondecode(data.aws_secretsmanager_secret_version.api_token.secret_string)["api_token"] } @@ -197,6 +197,16 @@ resource "traceable_session_identification_response_rule" "example" { operator = "EQUALS" value = "example-value" } + condition_response_cookie { + key = "X-Example-cookie" + operator = "EQUALS" + value = "example-value" + } + condition_response_cookie { + key = "X-Example-cookie-2" + operator = "EQUALS" + value = "example-value" + } } session_token_details { @@ -210,6 +220,11 @@ resource "traceable_session_identification_response_rule" "example" { expiration_type = "JWT" token_value_transformation_list { - json_path = "$.token" + json_path { + value="random" + } + regex_capture_group{ + value="(testing)" + } } } \ No newline at end of file diff --git a/provider/resource_session_identification_response_rule.go b/provider/resource_session_identification_response_rule.go index ec48b70..b023560 100644 --- a/provider/resource_session_identification_response_rule.go +++ b/provider/resource_session_identification_response_rule.go @@ -46,7 +46,7 @@ func resourceSessionIdentificationResponseRule() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, }, "token_extraction_condition_list": { - Type: schema.TypeList, + Type: schema.TypeSet, Description: "Conditions to satisfy for extracting Session Token", Optional: true, Elem: &schema.Resource{ @@ -119,7 +119,7 @@ func resourceSessionIdentificationResponseRule() *schema.Resource { Schema: map[string]*schema.Schema{ "token_key": { Type: schema.TypeString, - Description: "Test header key", + Description: "response header key", Required: true, }, "operator": { @@ -139,7 +139,7 @@ func resourceSessionIdentificationResponseRule() *schema.Resource { Schema: map[string]*schema.Schema{ "token_key": { Type: schema.TypeString, - Description: "Test cookie key", + Description: "response cookie key", Required: true, }, "operator": { @@ -171,32 +171,64 @@ func resourceSessionIdentificationResponseRule() *schema.Resource { Optional: true, }, "token_value_transformation_list": { - Type: schema.TypeList, - Description: "Conditions to satisfy for extracting Session Token", + Type: schema.TypeSet, + Description: "List of value transformations for the session token", Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "json_path": { - Type: schema.TypeString, - Description: "the json path group for value transformation", + Type: schema.TypeSet, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Type: schema.TypeString, + Description: "The json path value for transformation", + Required: true, + }, + }, + }, Optional: true, DiffSuppressFunc: suppressValueTransformationListDiff, }, "regex_capture_group": { - Type: schema.TypeString, - Description: "the regex capture group for value transformation", + Type: schema.TypeSet, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Type: schema.TypeString, + Description: "The regex capture group value for transformation", + Required: true, + }, + }, + }, Optional: true, DiffSuppressFunc: suppressValueTransformationListDiff, }, "jwt_payload_claim": { - Type: schema.TypeString, - Description: "the jwt payload claim for value transformation", + Type: schema.TypeSet, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Type: schema.TypeString, + Description: "The jwt payload claim value for transformation", + Required: true, + }, + }, + }, Optional: true, DiffSuppressFunc: suppressValueTransformationListDiff, }, "base64": { - Type: schema.TypeBool, - Description: "whether we use the base64 value transformation", + Type: schema.TypeSet, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Type: schema.TypeBool, + Description: "Whether the base64 value transformation is used", + Required: true, + }, + }, + }, Optional: true, DiffSuppressFunc: suppressValueTransformationListDiff, }, @@ -272,7 +304,7 @@ func resourceSessionIdentificationResponseRuleCreate(d *schema.ResourceData, met conditionListStr := "" if v, ok := d.GetOk("token_extraction_condition_list"); ok { - conditions := v.([]interface{}) + conditions := v.(*schema.Set).List() if len(conditions) > 0 { conditionListStr = `predicate: { predicateType: LOGICAL, logicalPredicate: { operator: AND, children: [` @@ -336,30 +368,49 @@ func resourceSessionIdentificationResponseRuleCreate(d *schema.ResourceData, met valueProjectionsStr := "" if v, ok := d.GetOk("token_value_transformation_list"); ok { - valueTransformations := v.([]interface{}) + valueTransformations := v.(*schema.Set).List() var valueProjections []string for _, transformation := range valueTransformations { transformationMap := transformation.(map[string]interface{}) - if jsonPath, ok := transformationMap["json_path"].(string); ok && jsonPath != "" { - valueProjections = append(valueProjections, fmt.Sprintf(`{ - valueProjectionType: JSON_PATH, - jsonPathProjection: { path: "%s" } - }`, jsonPath)) + if jsonPathSet, ok := transformationMap["json_path"].(*schema.Set); ok { + for _, jsonPath := range jsonPathSet.List() { + jsonPathMap := jsonPath.(map[string]interface{}) + value := jsonPathMap["value"].(string) + valueProjections = append(valueProjections, fmt.Sprintf(`{ + valueProjectionType: JSON_PATH, + jsonPathProjection: { path: "%s" } + }`, value)) + } } - if regexCaptureGroup, ok := transformationMap["regex_capture_group"].(string); ok && regexCaptureGroup != "" { - valueProjections = append(valueProjections, fmt.Sprintf(`{ - valueProjectionType: REGEX_CAPTURE_GROUP, - regexCaptureGroupProjection: { regexCaptureGroup: "%s" } - }`, regexCaptureGroup)) + if regexCaptureGroupSet, ok := transformationMap["regex_capture_group"].(*schema.Set); ok { + for _, regexCaptureGroup := range regexCaptureGroupSet.List() { + regexCaptureGroupMap := regexCaptureGroup.(map[string]interface{}) + value := regexCaptureGroupMap["value"].(string) + valueProjections = append(valueProjections, fmt.Sprintf(`{ + valueProjectionType: REGEX_CAPTURE_GROUP, + regexCaptureGroupProjection: { regexCaptureGroup: "%s" } + }`, value)) + } } - if jwtPayloadClaim, ok := transformationMap["jwt_payload_claim"].(string); ok && jwtPayloadClaim != "" { - valueProjections = append(valueProjections, fmt.Sprintf(`{ - valueProjectionType: JWT_PAYLOAD_CLAIM, - jwtPayloadClaimProjection: { claim: "%s" } - }`, jwtPayloadClaim)) + if jwtPayloadClaimSet, ok := transformationMap["jwt_payload_claim"].(*schema.Set); ok { + for _, jwtPayloadClaim := range jwtPayloadClaimSet.List() { + jwtPayloadClaimMap := jwtPayloadClaim.(map[string]interface{}) + value := jwtPayloadClaimMap["value"].(string) + valueProjections = append(valueProjections, fmt.Sprintf(`{ + valueProjectionType: JWT_PAYLOAD_CLAIM, + jwtPayloadClaimProjection: { claim: "%s" } + }`, value)) + } } - if base64, ok := transformationMap["base64"].(bool); ok && base64 { - valueProjections = append(valueProjections, `{ valueProjectionType: BASE64 }`) + if base64Set, ok := transformationMap["base64"].(*schema.Set); ok { + for _, base64 := range base64Set.List() { + base64Map := base64.(map[string]interface{}) + value := base64Map["value"].(bool) + valueProjections = append(valueProjections, fmt.Sprintf(`{ + valueProjectionType: BASE64, + base64: %t + }`, value)) + } } } if len(valueProjections) > 0 { @@ -401,7 +452,6 @@ func resourceSessionIdentificationResponseRuleCreate(d *schema.ResourceData, met `, name, descriptionStr, scopeStr, conditionListStr, responseAttributeKeyLocationStr, expirationTypeStr, obfuscationStr, tokenMatchConditionStr, valueProjectionsStr) var response map[string]interface{} - log.Printf(query) responseStr, err := executeQuery(query, meta) err = json.Unmarshal([]byte(responseStr), &response) if err != nil { @@ -419,64 +469,10 @@ func resourceSessionIdentificationResponseRuleCreate(d *schema.ResourceData, met } return nil - -} - -func buildStringArray(input []string) string { - if len(input) == 0 { - return "[]" - } - output := "[" - for _, v := range input { - output += fmt.Sprintf(`"%s",`, v) - } - output = output[:len(output)-1] // Remove trailing comma - output += "]" - return output -} - -func interfaceSliceToStringSlice(input []interface{}) []string { - var output []string - for _, v := range input { - output = append(output, v.(string)) - } - return output -} - -func buildConditionList(attributeType string, conditions []interface{}) string { - conditionStr := "" - for _, condition := range conditions { - conditionMap := condition.(map[string]interface{}) - key := conditionMap["key"].(string) - operator := conditionMap["operator"].(string) - value := conditionMap["value"].(string) - conditionStr += fmt.Sprintf(`{ - predicateType: ATTRIBUTE, - attributePredicate: { - attributeProjection: { - matchCondition: { - matchOperator: %s, - stringValue: "%s" - } - }, - matchCondition: { - matchOperator: %s, - stringValue: "%s" - }, - attributeKeyLocationType: RESPONSE, - responseAttributeKeyLocation: %s - } - },`, operator, key, operator, value, attributeType) - } - // Remove trailing comma - if len(conditionStr) > 0 { - conditionStr = conditionStr[:len(conditionStr)-1] - } - return conditionStr } func resourceSessionIdentificationResponseRuleRead(d *schema.ResourceData, meta interface{}) error { - readQuery := `{sessionIdentificationRulesV2{count results{id scope{environmentNames serviceNames urlMatchRegexes}description name sessionTokenRules{predicate{attributePredicate{attributeKeyLocationType attributeProjection{matchCondition{matchOperator stringValue}valueProjections{jsonPathProjection{path}jwtPayloadClaimProjection{claim}regexCaptureGroupProjection{regexCaptureGroup}valueProjectionType}}matchCondition{matchOperator stringValue}responseAttributeKeyLocation responseAttributeKeyLocation}customProjection{customJson}logicalPredicate{children{attributePredicate{attributeKeyLocationType attributeProjection{matchCondition{matchOperator stringValue}valueProjections{jsonPathProjection{path}jwtPayloadClaimProjection{claim}regexCaptureGroupProjection{regexCaptureGroup}valueProjectionType}}matchCondition{matchOperator stringValue}responseAttributeKeyLocation responseAttributeKeyLocation}predicateType}operator}predicateType}responseSessionTokenDetails{responseAttributeKeyLocation expirationType}responseSessionTokenDetails{attributeExpiration{expirationFormat projectionRoot{attributeProjection{matchCondition{matchOperator stringValue}valueProjections{jsonPathProjection{path}jwtPayloadClaimProjection{claim}regexCaptureGroupProjection{regexCaptureGroup}valueProjectionType}}customProjection{customJson}projectionType}responseAttributeKeyLocation}expirationType responseAttributeKeyLocation}sessionTokenValueRule{obfuscationStrategy projectionRoot{attributeProjection{matchCondition{matchOperator stringValue}valueProjections{jsonPathProjection{path}jwtPayloadClaimProjection{claim}regexCaptureGroupProjection{regexCaptureGroup}valueProjectionType}}customProjection{customJson}projectionType}}tokenType}status{disabled}}total}}` + readQuery := `{sessionIdentificationRulesV2{count results{id scope{environmentNames serviceNames urlMatchRegexes}description name sessionTokenRules{predicate{attributePredicate{attributeKeyLocationType attributeProjection{matchCondition{matchOperator stringValue}valueProjections{jsonPathProjection{path}jwtPayloadClaimProjection{claim}regexCaptureGroupProjection{regexCaptureGroup}valueProjectionType}}matchCondition{matchOperator stringValue}responseAttributeKeyLocation}logicalPredicate{children{attributePredicate{attributeKeyLocationType attributeProjection{matchCondition{matchOperator stringValue}valueProjections{jsonPathProjection{path}jwtPayloadClaimProjection{claim}regexCaptureGroupProjection{regexCaptureGroup}valueProjectionType}}matchCondition{matchOperator stringValue}responseAttributeKeyLocation}predicateType}operator}predicateType}responseSessionTokenDetails{responseAttributeKeyLocation expirationType}sessionTokenValueRule{obfuscationStrategy projectionRoot{attributeProjection{matchCondition{matchOperator stringValue}valueProjections{jsonPathProjection{path}jwtPayloadClaimProjection{claim}regexCaptureGroupProjection{regexCaptureGroup}valueProjectionType}}customProjection{customJson}projectionType}}tokenType}status{disabled}}total}}` var response map[string]interface{} responseStr, err := executeQuery(readQuery, meta) @@ -515,33 +511,33 @@ func resourceSessionIdentificationResponseRuleRead(d *schema.ResourceData, meta sessionTokenRules := ruleDetails["sessionTokenRules"].([]interface{}) if len(sessionTokenRules) > 0 { sessionTokenRule := sessionTokenRules[0].(map[string]interface{}) - if predicate, ok := sessionTokenRule["predicate"].(map[string]interface{}); ok { - if logicalPredicate, ok := predicate["logicalPredicate"].(map[string]interface{}); ok { - if children, ok := logicalPredicate["children"].([]interface{}); ok { - for _, child := range children { - childMap := child.(map[string]interface{}) - if attributePredicate, ok := childMap["attributePredicate"].(map[string]interface{}); ok { - if attributeProjection, ok := attributePredicate["attributeProjection"].(map[string]interface{}); ok { - if matchCondition, ok := attributeProjection["matchCondition"].(map[string]interface{}); ok { - d.Set("condition_response_header", map[string]interface{}{ - "key": matchCondition["stringValue"].(string), - "operator": matchCondition["matchOperator"].(string), - }) - } - } - } + if responseSessionTokenDetails, ok := sessionTokenRule["responseSessionTokenDetails"].(map[string]interface{}); ok { + var tokenDetails map[string]interface{} + if responseAttributeKeyLocation, ok := responseSessionTokenDetails["responseAttributeKeyLocation"].(string); ok { + switch responseAttributeKeyLocation { + case "HEADER": + if v, ok := responseSessionTokenDetails["token_response_header"]; ok { + tokenDetails = v.([]interface{})[0].(map[string]interface{}) + } + case "COOKIE": + if v, ok := responseSessionTokenDetails["token_response_cookie"]; ok { + tokenDetails = v.([]interface{})[0].(map[string]interface{}) } + } } - } - - if responseSessionTokenDetails, ok := sessionTokenRule["responseSessionTokenDetails"].(map[string]interface{}); ok { - responseAttributeKeyLocation := responseSessionTokenDetails["responseAttributeKeyLocation"].(string) - d.Set("session_token_details", map[string]interface{}{ - "token_response_header": map[string]interface{}{ - "responseAttributeKeyLocation": responseAttributeKeyLocation, - }, - }) + if tokenDetails != nil { + d.Set("session_token_details", []interface{}{ + map[string]interface{}{ + "token_key": tokenDetails["token_key"], + "operator": tokenDetails["operator"], + }, + }) + } + } else { + d.Set("name", "") + d.Set("description", "") + d.Set("session_token_details", "") } if sessionTokenValueRule, ok := sessionTokenRule["sessionTokenValueRule"].(map[string]interface{}); ok { @@ -555,25 +551,18 @@ func resourceSessionIdentificationResponseRuleRead(d *schema.ResourceData, meta if projectionRoot, ok := sessionTokenValueRule["projectionRoot"].(map[string]interface{}); ok { if attributeProjection, ok := projectionRoot["attributeProjection"].(map[string]interface{}); ok { if valueProjections, ok := attributeProjection["valueProjections"].([]interface{}); ok { - var transformations []interface{} - for _, valueProjection := range valueProjections { - valueProjectionMap := valueProjection.(map[string]interface{}) - transformation := make(map[string]interface{}) - switch valueProjectionMap["valueProjectionType"].(string) { - case "JSON_PATH": - transformation["json_path"] = valueProjectionMap["jsonPathProjection"].(map[string]interface{})["path"].(string) - case "REGEX_CAPTURE_GROUP": - transformation["regex_capture_group"] = valueProjectionMap["regexCaptureGroupProjection"].(map[string]interface{})["regexCaptureGroup"].(string) - case "JWT_PAYLOAD_CLAIM": - transformation["jwt_payload_claim"] = valueProjectionMap["jwtPayloadClaimProjection"].(map[string]interface{})["claim"].(string) - case "BASE64": - transformation["base64"] = true - } - transformations = append(transformations, transformation) - } - d.Set("token_value_transformation_list", transformations) + d.Set("token_value_transformation_list", valueProjections) + } else { + // If valueProjections is not present or empty, clear the token_value_transformation_list + d.Set("token_value_transformation_list", nil) } + } else { + // If attributeProjection is not present, clear the token_value_transformation_list + d.Set("token_value_transformation_list", nil) } + } else { + // If projectionRoot is not present, clear the token_value_transformation_list + d.Set("token_value_transformation_list", nil) } } } @@ -617,7 +606,7 @@ func resourceSessionIdentificationResponseRuleUpdate(d *schema.ResourceData, met conditionListStr := "" if v, ok := d.GetOk("token_extraction_condition_list"); ok { - conditions := v.([]interface{}) + conditions := v.(*schema.Set).List() if len(conditions) > 0 { conditionListStr = `predicate: { predicateType: LOGICAL, logicalPredicate: { operator: AND, children: [` @@ -653,73 +642,78 @@ func resourceSessionIdentificationResponseRuleUpdate(d *schema.ResourceData, met } responseAttributeKeyLocationStr := "" + tokenMatchConditionStr := "" if v, ok := d.GetOk("session_token_details"); ok { sessionTokenDetails := v.([]interface{})[0].(map[string]interface{}) - if tokenresponseHeader, ok := sessionTokenDetails["token_response_header"]; ok { - if len(tokenresponseHeader.(*schema.Set).List()) > 0 { + if tokenResponseHeader, ok := sessionTokenDetails["token_response_header"]; ok { + if len(tokenResponseHeader.(*schema.Set).List()) > 0 { responseAttributeKeyLocationStr = "HEADER" + tokenResponseHeaderList := tokenResponseHeader.(*schema.Set).List() + tokenKey := tokenResponseHeaderList[0].(map[string]interface{})["token_key"].(string) + operator := tokenResponseHeaderList[0].(map[string]interface{})["operator"].(string) + tokenMatchConditionStr = fmt.Sprintf(`matchCondition: { matchOperator: %s, stringValue: "%s" },`, operator, tokenKey) } - } else if tokenresponseCookie, ok := sessionTokenDetails["token_response_cookie"]; ok { - if len(tokenresponseCookie.(*schema.Set).List()) > 0 { + } else if tokenResponseCookie, ok := sessionTokenDetails["token_response_cookie"]; ok { + if len(tokenResponseCookie.(*schema.Set).List()) > 0 { responseAttributeKeyLocationStr = "COOKIE" - } - } else if tokenresponseBody, ok := sessionTokenDetails["token_response_body"].(bool); ok && tokenresponseBody { - responseAttributeKeyLocationStr = "BODY" - } - } - - tokenMatchConditionStr := "" - if responseAttributeKeyLocationStr != "BODY" && responseAttributeKeyLocationStr != "" { - if v, ok := d.GetOk("session_token_details"); ok { - sessionTokenDetails := v.([]interface{})[0].(map[string]interface{}) - - if tokenresponseHeader, ok := sessionTokenDetails["token_response_header"].([]interface{}); ok && len(tokenresponseHeader) > 0 { - tokenKey := tokenresponseHeader[0].(map[string]interface{})["token_key"].(string) - operator := tokenresponseHeader[0].(map[string]interface{})["operator"].(string) - tokenMatchConditionStr = fmt.Sprintf(`matchCondition: { matchOperator: %s, stringValue: "%s" },`, operator, tokenKey) - } else if tokenresponseCookie, ok := sessionTokenDetails["token_response_cookie"].([]interface{}); ok && len(tokenresponseCookie) > 0 { - tokenKey := tokenresponseCookie[0].(map[string]interface{})["token_key"].(string) - operator := tokenresponseCookie[0].(map[string]interface{})["operator"].(string) + tokenResponseCookieList := tokenResponseCookie.(*schema.Set).List() + tokenKey := tokenResponseCookieList[0].(map[string]interface{})["token_key"].(string) + operator := tokenResponseCookieList[0].(map[string]interface{})["operator"].(string) tokenMatchConditionStr = fmt.Sprintf(`matchCondition: { matchOperator: %s, stringValue: "%s" },`, operator, tokenKey) } + } else if tokenResponseBody, ok := sessionTokenDetails["token_response_body"].(bool); ok && tokenResponseBody { + responseAttributeKeyLocationStr = "BODY" } } valueProjectionsStr := "" if v, ok := d.GetOk("token_value_transformation_list"); ok { - valueTransformations := v.([]interface{}) + valueTransformations := v.(*schema.Set).List() var valueProjections []string - for _, transformation := range valueTransformations { transformationMap := transformation.(map[string]interface{}) - - if jsonPath, ok := transformationMap["json_path"].(string); ok && jsonPath != "" { - valueProjections = append(valueProjections, fmt.Sprintf(`{ - valueProjectionType: JSON_PATH, - jsonPathProjection: { path: "%s" } - }`, jsonPath)) + if jsonPathSet, ok := transformationMap["json_path"].(*schema.Set); ok { + for _, jsonPath := range jsonPathSet.List() { + jsonPathMap := jsonPath.(map[string]interface{}) + value := jsonPathMap["value"].(string) + valueProjections = append(valueProjections, fmt.Sprintf(`{ + valueProjectionType: JSON_PATH, + jsonPathProjection: { path: "%s" } + }`, value)) + } } - - if regexCaptureGroup, ok := transformationMap["regex_capture_group"].(string); ok && regexCaptureGroup != "" { - valueProjections = append(valueProjections, fmt.Sprintf(`{ - valueProjectionType: REGEX_CAPTURE_GROUP, - regexCaptureGroupProjection: { regexCaptureGroup: "%s" } - }`, regexCaptureGroup)) + if regexCaptureGroupSet, ok := transformationMap["regex_capture_group"].(*schema.Set); ok { + for _, regexCaptureGroup := range regexCaptureGroupSet.List() { + regexCaptureGroupMap := regexCaptureGroup.(map[string]interface{}) + value := regexCaptureGroupMap["value"].(string) + valueProjections = append(valueProjections, fmt.Sprintf(`{ + valueProjectionType: REGEX_CAPTURE_GROUP, + regexCaptureGroupProjection: { regexCaptureGroup: "%s" } + }`, value)) + } } - - if jwtPayloadClaim, ok := transformationMap["jwt_payload_claim"].(string); ok && jwtPayloadClaim != "" { - valueProjections = append(valueProjections, fmt.Sprintf(`{ - valueProjectionType: JWT_PAYLOAD_CLAIM, - jwtPayloadClaimProjection: { claim: "%s" } - }`, jwtPayloadClaim)) + if jwtPayloadClaimSet, ok := transformationMap["jwt_payload_claim"].(*schema.Set); ok { + for _, jwtPayloadClaim := range jwtPayloadClaimSet.List() { + jwtPayloadClaimMap := jwtPayloadClaim.(map[string]interface{}) + value := jwtPayloadClaimMap["value"].(string) + valueProjections = append(valueProjections, fmt.Sprintf(`{ + valueProjectionType: JWT_PAYLOAD_CLAIM, + jwtPayloadClaimProjection: { claim: "%s" } + }`, value)) + } } - - if base64, ok := transformationMap["base64"].(bool); ok && base64 { - valueProjections = append(valueProjections, `{ valueProjectionType: BASE64 }`) + if base64Set, ok := transformationMap["base64"].(*schema.Set); ok { + for _, base64 := range base64Set.List() { + base64Map := base64.(map[string]interface{}) + value := base64Map["value"].(bool) + valueProjections = append(valueProjections, fmt.Sprintf(`{ + valueProjectionType: BASE64, + base64: %t + }`, value)) + } } } - if len(valueProjections) > 0 { valueProjectionsStr = fmt.Sprintf("valueProjections: [%s]", strings.Join(valueProjections, ", ")) } @@ -812,3 +806,56 @@ func resourceSessionIdentificationResponseRuleDelete(d *schema.ResourceData, met d.SetId("") return nil } + +func buildStringArray(input []string) string { + if len(input) == 0 { + return "[]" + } + output := "[" + for _, v := range input { + output += fmt.Sprintf(`"%s",`, v) + } + output = output[:len(output)-1] // Remove trailing comma + output += "]" + return output +} + +func interfaceSliceToStringSlice(input []interface{}) []string { + var output []string + for _, v := range input { + output = append(output, v.(string)) + } + return output +} + +func buildConditionList(attributeType string, conditions []interface{}) string { + conditionStr := "" + for _, condition := range conditions { + conditionMap := condition.(map[string]interface{}) + key := conditionMap["key"].(string) + operator := conditionMap["operator"].(string) + value := conditionMap["value"].(string) + conditionStr += fmt.Sprintf(`{ + predicateType: ATTRIBUTE, + attributePredicate: { + attributeProjection: { + matchCondition: { + matchOperator: %s, + stringValue: "%s" + } + }, + matchCondition: { + matchOperator: %s, + stringValue: "%s" + }, + attributeKeyLocationType: RESPONSE, + responseAttributeKeyLocation: %s + } + },`, operator, key, operator, value, attributeType) + } + // Remove trailing comma + if len(conditionStr) > 0 { + conditionStr = conditionStr[:len(conditionStr)-1] + } + return conditionStr +}