From 475b9019c007b954d4e17bac7aa836afa4683c75 Mon Sep 17 00:00:00 2001 From: Antony Saba Date: Fri, 9 Jun 2023 14:41:30 -0600 Subject: [PATCH] Add compliance relationships between framework items and library items. --- docs/resources/compliance_relationship.md | 28 + .../TestComplianceRelationship_Basic.yaml | 939 ++++++++++++++++++ jupiterone/internal/client/compliance.graphql | 40 + jupiterone/internal/client/generated.go | 363 +++++++ jupiterone/modifiers.go | 3 + jupiterone/provider.go | 1 + .../resource_compliance_relationship.go | 233 +++++ .../resource_compliance_relationship_test.go | 221 +++++ jupiterone/resource_group.go | 5 + 9 files changed, 1833 insertions(+) create mode 100644 docs/resources/compliance_relationship.md create mode 100644 jupiterone/cassettes/TestComplianceRelationship_Basic.yaml create mode 100644 jupiterone/resource_compliance_relationship.go create mode 100644 jupiterone/resource_compliance_relationship_test.go diff --git a/docs/resources/compliance_relationship.md b/docs/resources/compliance_relationship.md new file mode 100644 index 00000000..8a6ff914 --- /dev/null +++ b/docs/resources/compliance_relationship.md @@ -0,0 +1,28 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "jupiterone_compliance_relationship Resource - terraform-provider-jupiterone" +subcategory: "" +description: |- + A relationship between a framework item (requirement) and a library item (control). +--- + +# jupiterone_compliance_relationship (Resource) + +A relationship between a framework item (requirement) and a library item (control). + + + + +## Schema + +### Required + +- `framework_item_id` (String) The id of the framework item (requirement) to link +- `library_item_id` (String) The id of the library item (control) to link +- `relationship_type` (String) Whether to INHERIT_EVIDENCE or IGNORE_EVIDENCE in the linked framework item + +### Read-Only + +- `id` (String) The ID of this resource. + + diff --git a/jupiterone/cassettes/TestComplianceRelationship_Basic.yaml b/jupiterone/cassettes/TestComplianceRelationship_Basic.yaml new file mode 100644 index 00000000..5543603f --- /dev/null +++ b/jupiterone/cassettes/TestComplianceRelationship_Basic.yaml @@ -0,0 +1,939 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 341 + transfer_encoding: [] + trailer: {} + host: graphql.us.jupiterone.io + remote_addr: "" + request_uri: "" + body: '{"query":"\nmutation CreateComplianceFramework ($framework: CreateComplianceFrameworkInput!) {\n\tcreateComplianceFramework(input: $framework) {\n\t\tid\n\t}\n}\n","variables":{"framework":{"name":"tf-acc-test-framework","version":"1","frameworkType":"STANDARD","webLink":"","scopeFilters":null}},"operationName":"CreateComplianceFramework"}' + form: {} + headers: + Cache-Control: + - no-cache + Content-Type: + - application/json + url: https://graphql.us.jupiterone.io/ + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 85 + uncompressed: false + body: | + {"data":{"createComplianceFramework":{"id":"9e8595a7-6b6e-42cc-8a29-c80e85f9e899"}}} + headers: + Access-Control-Allow-Credentials: + - "true" + Content-Length: + - "85" + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' https: data:;form-action ''self'';frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Content-Type: + - application/json + Cross-Origin-Embedder-Policy: + - require-corp + Cross-Origin-Opener-Policy: + - same-origin + Cross-Origin-Resource-Policy: + - same-origin + Expect-Ct: + - max-age=0 + Origin-Agent-Cluster: + - ?1 + Ratelimit-Limit: + - "1000" + Ratelimit-Remaining: + - "999" + Ratelimit-Requested: + - "1" + Ratelimit-Reset: + - "1" + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - "off" + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Xss-Protection: + - "0" + status: 200 OK + code: 200 + duration: 1.790640916s + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 339 + transfer_encoding: [] + trailer: {} + host: graphql.us.jupiterone.io + remote_addr: "" + request_uri: "" + body: '{"query":"\nmutation CreateComplianceGroup ($input: CreateComplianceGroupInput!) {\n\tcreateComplianceGroup(input: $input) {\n\t\tid\n\t}\n}\n","variables":{"input":{"name":"tf-acc-test-group","description":"","displayCategory":"","frameworkId":"9e8595a7-6b6e-42cc-8a29-c80e85f9e899","webLink":""}},"operationName":"CreateComplianceGroup"}' + form: {} + headers: + Cache-Control: + - no-cache + Content-Type: + - application/json + url: https://graphql.us.jupiterone.io/ + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 81 + uncompressed: false + body: | + {"data":{"createComplianceGroup":{"id":"daee12fb-be37-461f-b9e3-9ad96d778159"}}} + headers: + Access-Control-Allow-Credentials: + - "true" + Content-Length: + - "81" + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' https: data:;form-action ''self'';frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Content-Type: + - application/json + Cross-Origin-Embedder-Policy: + - require-corp + Cross-Origin-Opener-Policy: + - same-origin + Cross-Origin-Resource-Policy: + - same-origin + Expect-Ct: + - max-age=0 + Origin-Agent-Cluster: + - ?1 + Ratelimit-Limit: + - "1000" + Ratelimit-Remaining: + - "999" + Ratelimit-Requested: + - "1" + Ratelimit-Reset: + - "1" + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - "off" + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Xss-Protection: + - "0" + status: 200 OK + code: 200 + duration: 1.15666s + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 428 + transfer_encoding: [] + trailer: {} + host: graphql.us.jupiterone.io + remote_addr: "" + request_uri: "" + body: '{"query":"\nmutation CreateComplianceFrameworkItem ($input: CreateComplianceFrameworkItemInput!) {\n\tcreateComplianceFrameworkItem(input: $input) {\n\t\tid\n\t}\n}\n","variables":{"input":{"name":"tf-acc-test-item","description":"","displayCategory":"","ref":"","frameworkId":"9e8595a7-6b6e-42cc-8a29-c80e85f9e899","groupId":"daee12fb-be37-461f-b9e3-9ad96d778159","webLink":""}},"operationName":"CreateComplianceFrameworkItem"}' + form: {} + headers: + Cache-Control: + - no-cache + Content-Type: + - application/json + url: https://graphql.us.jupiterone.io/ + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 89 + uncompressed: false + body: | + {"data":{"createComplianceFrameworkItem":{"id":"58bf83f7-77bd-40d6-864a-34dc10dd5577"}}} + headers: + Access-Control-Allow-Credentials: + - "true" + Content-Length: + - "89" + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' https: data:;form-action ''self'';frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Content-Type: + - application/json + Cross-Origin-Embedder-Policy: + - require-corp + Cross-Origin-Opener-Policy: + - same-origin + Cross-Origin-Resource-Policy: + - same-origin + Expect-Ct: + - max-age=0 + Origin-Agent-Cluster: + - ?1 + Ratelimit-Limit: + - "1000" + Ratelimit-Remaining: + - "999" + Ratelimit-Requested: + - "1" + Ratelimit-Reset: + - "1" + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - "off" + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Xss-Protection: + - "0" + status: 200 OK + code: 200 + duration: 3.044858917s + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 321 + transfer_encoding: [] + trailer: {} + host: graphql.us.jupiterone.io + remote_addr: "" + request_uri: "" + body: '{"query":"\nmutation CreateComplianceLibraryItem ($input: CreateComplianceLibraryItemInput!) {\n\tcreateComplianceLibraryItem(input: $input) {\n\t\tid\n\t}\n}\n","variables":{"input":{"name":"tf-acc-test-control","description":"","displayCategory":"","ref":"","webLink":""}},"operationName":"CreateComplianceLibraryItem"}' + form: {} + headers: + Cache-Control: + - no-cache + Content-Type: + - application/json + url: https://graphql.us.jupiterone.io/ + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 87 + uncompressed: false + body: | + {"data":{"createComplianceLibraryItem":{"id":"a4f10947-b942-4c9a-9e47-b9beae67dac3"}}} + headers: + Access-Control-Allow-Credentials: + - "true" + Content-Length: + - "87" + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' https: data:;form-action ''self'';frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Content-Type: + - application/json + Cross-Origin-Embedder-Policy: + - require-corp + Cross-Origin-Opener-Policy: + - same-origin + Cross-Origin-Resource-Policy: + - same-origin + Expect-Ct: + - max-age=0 + Origin-Agent-Cluster: + - ?1 + Ratelimit-Limit: + - "1000" + Ratelimit-Remaining: + - "999" + Ratelimit-Requested: + - "1" + Ratelimit-Reset: + - "1" + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - "off" + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Xss-Protection: + - "0" + status: 200 OK + code: 200 + duration: 1.455908875s + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 496 + transfer_encoding: [] + trailer: {} + host: graphql.us.jupiterone.io + remote_addr: "" + request_uri: "" + body: '{"query":"\nmutation AttachComplianceLibraryItemToComplianceFrameworkItem ($input: AttachComplianceLibraryItemToComplianceFrameworkItemInput!) {\n\tattachComplianceLibraryItemToComplianceFrameworkItem(input: $input) {\n\t\trelationshipType\n\t}\n}\n","variables":{"input":{"frameworkItemId":"58bf83f7-77bd-40d6-864a-34dc10dd5577","libraryItemId":"a4f10947-b942-4c9a-9e47-b9beae67dac3","relationshipType":"INHERIT_EVIDENCE"}},"operationName":"AttachComplianceLibraryItemToComplianceFrameworkItem"}' + form: {} + headers: + Cache-Control: + - no-cache + Content-Type: + - application/json + url: https://graphql.us.jupiterone.io/ + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 106 + uncompressed: false + body: | + {"data":{"attachComplianceLibraryItemToComplianceFrameworkItem":{"relationshipType":"INHERIT_EVIDENCE"}}} + headers: + Access-Control-Allow-Credentials: + - "true" + Content-Length: + - "106" + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' https: data:;form-action ''self'';frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Content-Type: + - application/json + Cross-Origin-Embedder-Policy: + - require-corp + Cross-Origin-Opener-Policy: + - same-origin + Cross-Origin-Resource-Policy: + - same-origin + Expect-Ct: + - max-age=0 + Origin-Agent-Cluster: + - ?1 + Ratelimit-Limit: + - "1000" + Ratelimit-Remaining: + - "999" + Ratelimit-Requested: + - "1" + Ratelimit-Reset: + - "1" + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - "off" + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Xss-Protection: + - "0" + status: 200 OK + code: 200 + duration: 493.763625ms + - id: 5 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 397 + transfer_encoding: [] + trailer: {} + host: graphql.us.jupiterone.io + remote_addr: "" + request_uri: "" + body: '{"query":"\nquery GetComplianceFrameworkItemRelationshipsById ($id: ID!) {\n\tcomplianceFrameworkItem(input: {id:$id}) {\n\t\tlibraryItems {\n\t\t\tinheritedEvidenceLibraryItems {\n\t\t\t\tid\n\t\t\t}\n\t\t\tignoredEvidenceLibraryItems {\n\t\t\t\tid\n\t\t\t}\n\t\t}\n\t}\n}\n","variables":{"id":"58bf83f7-77bd-40d6-864a-34dc10dd5577"},"operationName":"GetComplianceFrameworkItemRelationshipsById"}' + form: {} + headers: + Cache-Control: + - no-cache + Content-Type: + - application/json + url: https://graphql.us.jupiterone.io/ + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 169 + uncompressed: false + body: | + {"data":{"complianceFrameworkItem":{"libraryItems":{"inheritedEvidenceLibraryItems":[{"id":"a4f10947-b942-4c9a-9e47-b9beae67dac3"}],"ignoredEvidenceLibraryItems":[]}}}} + headers: + Access-Control-Allow-Credentials: + - "true" + Content-Length: + - "169" + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' https: data:;form-action ''self'';frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Content-Type: + - application/json + Cross-Origin-Embedder-Policy: + - require-corp + Cross-Origin-Opener-Policy: + - same-origin + Cross-Origin-Resource-Policy: + - same-origin + Expect-Ct: + - max-age=0 + Origin-Agent-Cluster: + - ?1 + Ratelimit-Limit: + - "1000" + Ratelimit-Remaining: + - "999" + Ratelimit-Requested: + - "1" + Ratelimit-Reset: + - "1" + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - "off" + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Xss-Protection: + - "0" + status: 200 OK + code: 200 + duration: 1.040642959s + - id: 6 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 397 + transfer_encoding: [] + trailer: {} + host: graphql.us.jupiterone.io + remote_addr: "" + request_uri: "" + body: '{"query":"\nquery GetComplianceFrameworkItemRelationshipsById ($id: ID!) {\n\tcomplianceFrameworkItem(input: {id:$id}) {\n\t\tlibraryItems {\n\t\t\tinheritedEvidenceLibraryItems {\n\t\t\t\tid\n\t\t\t}\n\t\t\tignoredEvidenceLibraryItems {\n\t\t\t\tid\n\t\t\t}\n\t\t}\n\t}\n}\n","variables":{"id":"58bf83f7-77bd-40d6-864a-34dc10dd5577"},"operationName":"GetComplianceFrameworkItemRelationshipsById"}' + form: {} + headers: + Cache-Control: + - no-cache + Content-Type: + - application/json + url: https://graphql.us.jupiterone.io/ + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 169 + uncompressed: false + body: | + {"data":{"complianceFrameworkItem":{"libraryItems":{"inheritedEvidenceLibraryItems":[{"id":"a4f10947-b942-4c9a-9e47-b9beae67dac3"}],"ignoredEvidenceLibraryItems":[]}}}} + headers: + Access-Control-Allow-Credentials: + - "true" + Content-Length: + - "169" + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' https: data:;form-action ''self'';frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Content-Type: + - application/json + Cross-Origin-Embedder-Policy: + - require-corp + Cross-Origin-Opener-Policy: + - same-origin + Cross-Origin-Resource-Policy: + - same-origin + Expect-Ct: + - max-age=0 + Origin-Agent-Cluster: + - ?1 + Ratelimit-Limit: + - "1000" + Ratelimit-Remaining: + - "999" + Ratelimit-Requested: + - "1" + Ratelimit-Reset: + - "1" + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - "off" + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Xss-Protection: + - "0" + status: 200 OK + code: 200 + duration: 1.140330208s + - id: 7 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 555 + transfer_encoding: [] + trailer: {} + host: graphql.us.jupiterone.io + remote_addr: "" + request_uri: "" + body: '{"query":"\nmutation UpdateComplianceLibraryItemToComplianceFrameworkItemRelationship ($input: UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput!) {\n\tupdateComplianceLibraryItemToComplianceFrameworkItemRelationship(input: $input) {\n\t\trelationshipType\n\t}\n}\n","variables":{"input":{"frameworkItemId":"58bf83f7-77bd-40d6-864a-34dc10dd5577","libraryItemId":"a4f10947-b942-4c9a-9e47-b9beae67dac3","updates":{"relationshipType":"IGNORE_EVIDENCE"}}},"operationName":"UpdateComplianceLibraryItemToComplianceFrameworkItemRelationship"}' + form: {} + headers: + Cache-Control: + - no-cache + Content-Type: + - application/json + url: https://graphql.us.jupiterone.io/ + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 117 + uncompressed: false + body: | + {"data":{"updateComplianceLibraryItemToComplianceFrameworkItemRelationship":{"relationshipType":"IGNORE_EVIDENCE"}}} + headers: + Access-Control-Allow-Credentials: + - "true" + Content-Length: + - "117" + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' https: data:;form-action ''self'';frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Content-Type: + - application/json + Cross-Origin-Embedder-Policy: + - require-corp + Cross-Origin-Opener-Policy: + - same-origin + Cross-Origin-Resource-Policy: + - same-origin + Expect-Ct: + - max-age=0 + Origin-Agent-Cluster: + - ?1 + Ratelimit-Limit: + - "1000" + Ratelimit-Remaining: + - "999" + Ratelimit-Requested: + - "1" + Ratelimit-Reset: + - "1" + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - "off" + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Xss-Protection: + - "0" + status: 200 OK + code: 200 + duration: 1.195835792s + - id: 8 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 397 + transfer_encoding: [] + trailer: {} + host: graphql.us.jupiterone.io + remote_addr: "" + request_uri: "" + body: '{"query":"\nquery GetComplianceFrameworkItemRelationshipsById ($id: ID!) {\n\tcomplianceFrameworkItem(input: {id:$id}) {\n\t\tlibraryItems {\n\t\t\tinheritedEvidenceLibraryItems {\n\t\t\t\tid\n\t\t\t}\n\t\t\tignoredEvidenceLibraryItems {\n\t\t\t\tid\n\t\t\t}\n\t\t}\n\t}\n}\n","variables":{"id":"58bf83f7-77bd-40d6-864a-34dc10dd5577"},"operationName":"GetComplianceFrameworkItemRelationshipsById"}' + form: {} + headers: + Cache-Control: + - no-cache + Content-Type: + - application/json + url: https://graphql.us.jupiterone.io/ + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 169 + uncompressed: false + body: | + {"data":{"complianceFrameworkItem":{"libraryItems":{"inheritedEvidenceLibraryItems":[],"ignoredEvidenceLibraryItems":[{"id":"a4f10947-b942-4c9a-9e47-b9beae67dac3"}]}}}} + headers: + Access-Control-Allow-Credentials: + - "true" + Content-Length: + - "169" + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' https: data:;form-action ''self'';frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Content-Type: + - application/json + Cross-Origin-Embedder-Policy: + - require-corp + Cross-Origin-Opener-Policy: + - same-origin + Cross-Origin-Resource-Policy: + - same-origin + Expect-Ct: + - max-age=0 + Origin-Agent-Cluster: + - ?1 + Ratelimit-Limit: + - "1000" + Ratelimit-Remaining: + - "999" + Ratelimit-Requested: + - "1" + Ratelimit-Reset: + - "1" + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - "off" + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Xss-Protection: + - "0" + status: 200 OK + code: 200 + duration: 963.460667ms + - id: 9 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 466 + transfer_encoding: [] + trailer: {} + host: graphql.us.jupiterone.io + remote_addr: "" + request_uri: "" + body: '{"query":"\nmutation DetachComplianceLibraryItemFromComplianceFrameworkItem ($input: DetachComplianceLibraryItemFromComplianceFrameworkItemInput!) {\n\tdetachComplianceLibraryItemFromComplianceFrameworkItem(input: $input) {\n\t\trelationshipType\n\t}\n}\n","variables":{"input":{"frameworkItemId":"58bf83f7-77bd-40d6-864a-34dc10dd5577","libraryItemId":"a4f10947-b942-4c9a-9e47-b9beae67dac3"}},"operationName":"DetachComplianceLibraryItemFromComplianceFrameworkItem"}' + form: {} + headers: + Cache-Control: + - no-cache + Content-Type: + - application/json + url: https://graphql.us.jupiterone.io/ + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 107 + uncompressed: false + body: | + {"data":{"detachComplianceLibraryItemFromComplianceFrameworkItem":{"relationshipType":"IGNORE_EVIDENCE"}}} + headers: + Access-Control-Allow-Credentials: + - "true" + Content-Length: + - "107" + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' https: data:;form-action ''self'';frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Content-Type: + - application/json + Cross-Origin-Embedder-Policy: + - require-corp + Cross-Origin-Opener-Policy: + - same-origin + Cross-Origin-Resource-Policy: + - same-origin + Expect-Ct: + - max-age=0 + Origin-Agent-Cluster: + - ?1 + Ratelimit-Limit: + - "1000" + Ratelimit-Remaining: + - "999" + Ratelimit-Requested: + - "1" + Ratelimit-Reset: + - "1" + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - "off" + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Xss-Protection: + - "0" + status: 200 OK + code: 200 + duration: 468.120375ms + - id: 10 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 253 + transfer_encoding: [] + trailer: {} + host: graphql.us.jupiterone.io + remote_addr: "" + request_uri: "" + body: '{"query":"\nmutation DeleteComplianceFramework ($input: DeleteComplianceFrameworkInput!) {\n\tdeleteComplianceFramework(input: $input)\n}\n","variables":{"input":{"id":"9e8595a7-6b6e-42cc-8a29-c80e85f9e899"}},"operationName":"DeleteComplianceFramework"}' + form: {} + headers: + Cache-Control: + - no-cache + Content-Type: + - application/json + url: https://graphql.us.jupiterone.io/ + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 78 + uncompressed: false + body: | + {"data":{"deleteComplianceFramework":"9e8595a7-6b6e-42cc-8a29-c80e85f9e899"}} + headers: + Access-Control-Allow-Credentials: + - "true" + Content-Length: + - "78" + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' https: data:;form-action ''self'';frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Content-Type: + - application/json + Cross-Origin-Embedder-Policy: + - require-corp + Cross-Origin-Opener-Policy: + - same-origin + Cross-Origin-Resource-Policy: + - same-origin + Expect-Ct: + - max-age=0 + Origin-Agent-Cluster: + - ?1 + Ratelimit-Limit: + - "1000" + Ratelimit-Remaining: + - "999" + Ratelimit-Requested: + - "1" + Ratelimit-Reset: + - "1" + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - "off" + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Xss-Protection: + - "0" + status: 200 OK + code: 200 + duration: 1.099630833s + - id: 11 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 220 + transfer_encoding: [] + trailer: {} + host: graphql.us.jupiterone.io + remote_addr: "" + request_uri: "" + body: '{"query":"\nmutation DeleteComplianceLibraryItem ($id: ID!) {\n\tdeleteComplianceLibraryItem(input: {id:$id})\n}\n","variables":{"id":"a4f10947-b942-4c9a-9e47-b9beae67dac3"},"operationName":"DeleteComplianceLibraryItem"}' + form: {} + headers: + Cache-Control: + - no-cache + Content-Type: + - application/json + url: https://graphql.us.jupiterone.io/ + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 80 + uncompressed: false + body: | + {"data":{"deleteComplianceLibraryItem":"a4f10947-b942-4c9a-9e47-b9beae67dac3"}} + headers: + Access-Control-Allow-Credentials: + - "true" + Content-Length: + - "80" + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' https: data:;form-action ''self'';frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Content-Type: + - application/json + Cross-Origin-Embedder-Policy: + - require-corp + Cross-Origin-Opener-Policy: + - same-origin + Cross-Origin-Resource-Policy: + - same-origin + Expect-Ct: + - max-age=0 + Origin-Agent-Cluster: + - ?1 + Ratelimit-Limit: + - "1000" + Ratelimit-Remaining: + - "999" + Ratelimit-Requested: + - "1" + Ratelimit-Reset: + - "1" + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - "off" + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Xss-Protection: + - "0" + status: 200 OK + code: 200 + duration: 439.663ms diff --git a/jupiterone/internal/client/compliance.graphql b/jupiterone/internal/client/compliance.graphql index ef45187b..c35088d2 100644 --- a/jupiterone/internal/client/compliance.graphql +++ b/jupiterone/internal/client/compliance.graphql @@ -129,3 +129,43 @@ mutation UpdateComplianceLibraryItem( id } } + +mutation AttachComplianceLibraryItemToComplianceFrameworkItem( + $input: AttachComplianceLibraryItemToComplianceFrameworkItemInput! +) { + attachComplianceLibraryItemToComplianceFrameworkItem(input: $input) { + relationshipType + } +} + +mutation UpdateComplianceLibraryItemToComplianceFrameworkItemRelationship( + $input: UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput! +) { + updateComplianceLibraryItemToComplianceFrameworkItemRelationship( + input: $input + ) { + relationshipType + } +} + +mutation DetachComplianceLibraryItemFromComplianceFrameworkItem( + $input: DetachComplianceLibraryItemFromComplianceFrameworkItemInput! +) { + detachComplianceLibraryItemFromComplianceFrameworkItem(input: $input) { + relationshipType + } +} + +# this query for for use in compliance_relationship resource reads +query GetComplianceFrameworkItemRelationshipsById($id: ID!) { + complianceFrameworkItem(input: { id: $id }) { + libraryItems { + inheritedEvidenceLibraryItems { + id + } + ignoredEvidenceLibraryItems { + id + } + } + } +} diff --git a/jupiterone/internal/client/generated.go b/jupiterone/internal/client/generated.go index fbc510f2..e1976658 100644 --- a/jupiterone/internal/client/generated.go +++ b/jupiterone/internal/client/generated.go @@ -8,6 +8,47 @@ import ( "github.com/Khan/genqlient/graphql" ) +// AttachComplianceLibraryItemToComplianceFrameworkItemAttachComplianceLibraryItemToComplianceFrameworkItemComplianceLibraryItemToComplianceFrameworkItemRelationship includes the requested fields of the GraphQL type ComplianceLibraryItemToComplianceFrameworkItemRelationship. +type AttachComplianceLibraryItemToComplianceFrameworkItemAttachComplianceLibraryItemToComplianceFrameworkItemComplianceLibraryItemToComplianceFrameworkItemRelationship struct { + RelationshipType LibraryItemToFrameworkItemRelationshipType `json:"relationshipType"` +} + +// GetRelationshipType returns AttachComplianceLibraryItemToComplianceFrameworkItemAttachComplianceLibraryItemToComplianceFrameworkItemComplianceLibraryItemToComplianceFrameworkItemRelationship.RelationshipType, and is useful for accessing the field via an interface. +func (v *AttachComplianceLibraryItemToComplianceFrameworkItemAttachComplianceLibraryItemToComplianceFrameworkItemComplianceLibraryItemToComplianceFrameworkItemRelationship) GetRelationshipType() LibraryItemToFrameworkItemRelationshipType { + return v.RelationshipType +} + +type AttachComplianceLibraryItemToComplianceFrameworkItemInput struct { + FrameworkItemId string `json:"frameworkItemId"` + LibraryItemId string `json:"libraryItemId"` + RelationshipType LibraryItemToFrameworkItemRelationshipType `json:"relationshipType"` +} + +// GetFrameworkItemId returns AttachComplianceLibraryItemToComplianceFrameworkItemInput.FrameworkItemId, and is useful for accessing the field via an interface. +func (v *AttachComplianceLibraryItemToComplianceFrameworkItemInput) GetFrameworkItemId() string { + return v.FrameworkItemId +} + +// GetLibraryItemId returns AttachComplianceLibraryItemToComplianceFrameworkItemInput.LibraryItemId, and is useful for accessing the field via an interface. +func (v *AttachComplianceLibraryItemToComplianceFrameworkItemInput) GetLibraryItemId() string { + return v.LibraryItemId +} + +// GetRelationshipType returns AttachComplianceLibraryItemToComplianceFrameworkItemInput.RelationshipType, and is useful for accessing the field via an interface. +func (v *AttachComplianceLibraryItemToComplianceFrameworkItemInput) GetRelationshipType() LibraryItemToFrameworkItemRelationshipType { + return v.RelationshipType +} + +// AttachComplianceLibraryItemToComplianceFrameworkItemResponse is returned by AttachComplianceLibraryItemToComplianceFrameworkItem on success. +type AttachComplianceLibraryItemToComplianceFrameworkItemResponse struct { + AttachComplianceLibraryItemToComplianceFrameworkItem AttachComplianceLibraryItemToComplianceFrameworkItemAttachComplianceLibraryItemToComplianceFrameworkItemComplianceLibraryItemToComplianceFrameworkItemRelationship `json:"attachComplianceLibraryItemToComplianceFrameworkItem"` +} + +// GetAttachComplianceLibraryItemToComplianceFrameworkItem returns AttachComplianceLibraryItemToComplianceFrameworkItemResponse.AttachComplianceLibraryItemToComplianceFrameworkItem, and is useful for accessing the field via an interface. +func (v *AttachComplianceLibraryItemToComplianceFrameworkItemResponse) GetAttachComplianceLibraryItemToComplianceFrameworkItem() AttachComplianceLibraryItemToComplianceFrameworkItemAttachComplianceLibraryItemToComplianceFrameworkItemComplianceLibraryItemToComplianceFrameworkItemRelationship { + return v.AttachComplianceLibraryItemToComplianceFrameworkItem +} + type ComplianceFrameworkItemAuditStatus string const ( @@ -635,6 +676,41 @@ func (v *DeleteRuleInstanceResponse) GetDeleteRuleInstance() DeleteRuleInstanceD return v.DeleteRuleInstance } +// DetachComplianceLibraryItemFromComplianceFrameworkItemDetachComplianceLibraryItemFromComplianceFrameworkItemComplianceLibraryItemToComplianceFrameworkItemRelationship includes the requested fields of the GraphQL type ComplianceLibraryItemToComplianceFrameworkItemRelationship. +type DetachComplianceLibraryItemFromComplianceFrameworkItemDetachComplianceLibraryItemFromComplianceFrameworkItemComplianceLibraryItemToComplianceFrameworkItemRelationship struct { + RelationshipType LibraryItemToFrameworkItemRelationshipType `json:"relationshipType"` +} + +// GetRelationshipType returns DetachComplianceLibraryItemFromComplianceFrameworkItemDetachComplianceLibraryItemFromComplianceFrameworkItemComplianceLibraryItemToComplianceFrameworkItemRelationship.RelationshipType, and is useful for accessing the field via an interface. +func (v *DetachComplianceLibraryItemFromComplianceFrameworkItemDetachComplianceLibraryItemFromComplianceFrameworkItemComplianceLibraryItemToComplianceFrameworkItemRelationship) GetRelationshipType() LibraryItemToFrameworkItemRelationshipType { + return v.RelationshipType +} + +type DetachComplianceLibraryItemFromComplianceFrameworkItemInput struct { + FrameworkItemId string `json:"frameworkItemId"` + LibraryItemId string `json:"libraryItemId"` +} + +// GetFrameworkItemId returns DetachComplianceLibraryItemFromComplianceFrameworkItemInput.FrameworkItemId, and is useful for accessing the field via an interface. +func (v *DetachComplianceLibraryItemFromComplianceFrameworkItemInput) GetFrameworkItemId() string { + return v.FrameworkItemId +} + +// GetLibraryItemId returns DetachComplianceLibraryItemFromComplianceFrameworkItemInput.LibraryItemId, and is useful for accessing the field via an interface. +func (v *DetachComplianceLibraryItemFromComplianceFrameworkItemInput) GetLibraryItemId() string { + return v.LibraryItemId +} + +// DetachComplianceLibraryItemFromComplianceFrameworkItemResponse is returned by DetachComplianceLibraryItemFromComplianceFrameworkItem on success. +type DetachComplianceLibraryItemFromComplianceFrameworkItemResponse struct { + DetachComplianceLibraryItemFromComplianceFrameworkItem DetachComplianceLibraryItemFromComplianceFrameworkItemDetachComplianceLibraryItemFromComplianceFrameworkItemComplianceLibraryItemToComplianceFrameworkItemRelationship `json:"detachComplianceLibraryItemFromComplianceFrameworkItem"` +} + +// GetDetachComplianceLibraryItemFromComplianceFrameworkItem returns DetachComplianceLibraryItemFromComplianceFrameworkItemResponse.DetachComplianceLibraryItemFromComplianceFrameworkItem, and is useful for accessing the field via an interface. +func (v *DetachComplianceLibraryItemFromComplianceFrameworkItemResponse) GetDetachComplianceLibraryItemFromComplianceFrameworkItem() DetachComplianceLibraryItemFromComplianceFrameworkItemDetachComplianceLibraryItemFromComplianceFrameworkItemComplianceLibraryItemToComplianceFrameworkItemRelationship { + return v.DetachComplianceLibraryItemFromComplianceFrameworkItem +} + // GetComplianceFrameworkByIdComplianceFramework includes the requested fields of the GraphQL type ComplianceFramework. type GetComplianceFrameworkByIdComplianceFramework struct { Id string `json:"id"` @@ -759,6 +835,62 @@ func (v *GetComplianceFrameworkItemByIdResponse) GetComplianceFrameworkItem() Ge return v.ComplianceFrameworkItem } +// GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItem includes the requested fields of the GraphQL type ComplianceFrameworkItem. +type GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItem struct { + LibraryItems GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItem `json:"libraryItems"` +} + +// GetLibraryItems returns GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItem.LibraryItems, and is useful for accessing the field via an interface. +func (v *GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItem) GetLibraryItems() GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItem { + return v.LibraryItems +} + +// GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItem includes the requested fields of the GraphQL type ComplianceLibraryItemsForFrameworkItem. +type GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItem struct { + InheritedEvidenceLibraryItems []GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItemInheritedEvidenceLibraryItemsComplianceLibraryItem `json:"inheritedEvidenceLibraryItems"` + IgnoredEvidenceLibraryItems []GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItemIgnoredEvidenceLibraryItemsComplianceLibraryItem `json:"ignoredEvidenceLibraryItems"` +} + +// GetInheritedEvidenceLibraryItems returns GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItem.InheritedEvidenceLibraryItems, and is useful for accessing the field via an interface. +func (v *GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItem) GetInheritedEvidenceLibraryItems() []GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItemInheritedEvidenceLibraryItemsComplianceLibraryItem { + return v.InheritedEvidenceLibraryItems +} + +// GetIgnoredEvidenceLibraryItems returns GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItem.IgnoredEvidenceLibraryItems, and is useful for accessing the field via an interface. +func (v *GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItem) GetIgnoredEvidenceLibraryItems() []GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItemIgnoredEvidenceLibraryItemsComplianceLibraryItem { + return v.IgnoredEvidenceLibraryItems +} + +// GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItemIgnoredEvidenceLibraryItemsComplianceLibraryItem includes the requested fields of the GraphQL type ComplianceLibraryItem. +type GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItemIgnoredEvidenceLibraryItemsComplianceLibraryItem struct { + Id string `json:"id"` +} + +// GetId returns GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItemIgnoredEvidenceLibraryItemsComplianceLibraryItem.Id, and is useful for accessing the field via an interface. +func (v *GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItemIgnoredEvidenceLibraryItemsComplianceLibraryItem) GetId() string { + return v.Id +} + +// GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItemInheritedEvidenceLibraryItemsComplianceLibraryItem includes the requested fields of the GraphQL type ComplianceLibraryItem. +type GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItemInheritedEvidenceLibraryItemsComplianceLibraryItem struct { + Id string `json:"id"` +} + +// GetId returns GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItemInheritedEvidenceLibraryItemsComplianceLibraryItem.Id, and is useful for accessing the field via an interface. +func (v *GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItemLibraryItemsComplianceLibraryItemsForFrameworkItemInheritedEvidenceLibraryItemsComplianceLibraryItem) GetId() string { + return v.Id +} + +// GetComplianceFrameworkItemRelationshipsByIdResponse is returned by GetComplianceFrameworkItemRelationshipsById on success. +type GetComplianceFrameworkItemRelationshipsByIdResponse struct { + ComplianceFrameworkItem GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItem `json:"complianceFrameworkItem"` +} + +// GetComplianceFrameworkItem returns GetComplianceFrameworkItemRelationshipsByIdResponse.ComplianceFrameworkItem, and is useful for accessing the field via an interface. +func (v *GetComplianceFrameworkItemRelationshipsByIdResponse) GetComplianceFrameworkItem() GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItem { + return v.ComplianceFrameworkItem +} + // GetComplianceGroupsComplianceFramework includes the requested fields of the GraphQL type ComplianceFramework. type GetComplianceGroupsComplianceFramework struct { Groups []ComplianceGroup `json:"groups"` @@ -1014,6 +1146,13 @@ func (v *J1QueryInput) GetVersion() string { return v.Version } // GetIncludeDeleted returns J1QueryInput.IncludeDeleted, and is useful for accessing the field via an interface. func (v *J1QueryInput) GetIncludeDeleted() bool { return v.IncludeDeleted } +type LibraryItemToFrameworkItemRelationshipType string + +const ( + LibraryItemToFrameworkItemRelationshipTypeIgnoreEvidence LibraryItemToFrameworkItemRelationshipType = "IGNORE_EVIDENCE" + LibraryItemToFrameworkItemRelationshipTypeInheritEvidence LibraryItemToFrameworkItemRelationshipType = "INHERIT_EVIDENCE" +) + type QueryResultsAre string const ( @@ -1423,6 +1562,56 @@ func (v *UpdateComplianceLibraryItemResponse) GetUpdateComplianceLibraryItem() U return v.UpdateComplianceLibraryItem } +type UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipFields struct { + RelationshipType LibraryItemToFrameworkItemRelationshipType `json:"relationshipType"` +} + +// GetRelationshipType returns UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipFields.RelationshipType, and is useful for accessing the field via an interface. +func (v *UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipFields) GetRelationshipType() LibraryItemToFrameworkItemRelationshipType { + return v.RelationshipType +} + +type UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput struct { + FrameworkItemId string `json:"frameworkItemId"` + LibraryItemId string `json:"libraryItemId"` + Updates UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipFields `json:"updates"` +} + +// GetFrameworkItemId returns UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput.FrameworkItemId, and is useful for accessing the field via an interface. +func (v *UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput) GetFrameworkItemId() string { + return v.FrameworkItemId +} + +// GetLibraryItemId returns UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput.LibraryItemId, and is useful for accessing the field via an interface. +func (v *UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput) GetLibraryItemId() string { + return v.LibraryItemId +} + +// GetUpdates returns UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput.Updates, and is useful for accessing the field via an interface. +func (v *UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput) GetUpdates() UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipFields { + return v.Updates +} + +// UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipResponse is returned by UpdateComplianceLibraryItemToComplianceFrameworkItemRelationship on success. +type UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipResponse struct { + UpdateComplianceLibraryItemToComplianceFrameworkItemRelationship UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipUpdateComplianceLibraryItemToComplianceFrameworkItemRelationship `json:"updateComplianceLibraryItemToComplianceFrameworkItemRelationship"` +} + +// GetUpdateComplianceLibraryItemToComplianceFrameworkItemRelationship returns UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipResponse.UpdateComplianceLibraryItemToComplianceFrameworkItemRelationship, and is useful for accessing the field via an interface. +func (v *UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipResponse) GetUpdateComplianceLibraryItemToComplianceFrameworkItemRelationship() UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipUpdateComplianceLibraryItemToComplianceFrameworkItemRelationship { + return v.UpdateComplianceLibraryItemToComplianceFrameworkItemRelationship +} + +// UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipUpdateComplianceLibraryItemToComplianceFrameworkItemRelationship includes the requested fields of the GraphQL type ComplianceLibraryItemToComplianceFrameworkItemRelationship. +type UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipUpdateComplianceLibraryItemToComplianceFrameworkItemRelationship struct { + RelationshipType LibraryItemToFrameworkItemRelationshipType `json:"relationshipType"` +} + +// GetRelationshipType returns UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipUpdateComplianceLibraryItemToComplianceFrameworkItemRelationship.RelationshipType, and is useful for accessing the field via an interface. +func (v *UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipUpdateComplianceLibraryItemToComplianceFrameworkItemRelationship) GetRelationshipType() LibraryItemToFrameworkItemRelationshipType { + return v.RelationshipType +} + // UpdateComplianceLibraryItemUpdateComplianceLibraryItem includes the requested fields of the GraphQL type ComplianceLibraryItem. type UpdateComplianceLibraryItemUpdateComplianceLibraryItem struct { Id string `json:"id"` @@ -1671,6 +1860,16 @@ func (v *UpdateReferencedQuestionRuleInstanceUpdateReferencedQuestionRuleInstanc return v.Operations } +// __AttachComplianceLibraryItemToComplianceFrameworkItemInput is used internally by genqlient +type __AttachComplianceLibraryItemToComplianceFrameworkItemInput struct { + Input AttachComplianceLibraryItemToComplianceFrameworkItemInput `json:"input"` +} + +// GetInput returns __AttachComplianceLibraryItemToComplianceFrameworkItemInput.Input, and is useful for accessing the field via an interface. +func (v *__AttachComplianceLibraryItemToComplianceFrameworkItemInput) GetInput() AttachComplianceLibraryItemToComplianceFrameworkItemInput { + return v.Input +} + // __CreateComplianceFrameworkInput is used internally by genqlient type __CreateComplianceFrameworkInput struct { Framework CreateComplianceFrameworkInput `json:"framework"` @@ -1785,6 +1984,16 @@ type __DeleteRuleInstanceInput struct { // GetId returns __DeleteRuleInstanceInput.Id, and is useful for accessing the field via an interface. func (v *__DeleteRuleInstanceInput) GetId() string { return v.Id } +// __DetachComplianceLibraryItemFromComplianceFrameworkItemInput is used internally by genqlient +type __DetachComplianceLibraryItemFromComplianceFrameworkItemInput struct { + Input DetachComplianceLibraryItemFromComplianceFrameworkItemInput `json:"input"` +} + +// GetInput returns __DetachComplianceLibraryItemFromComplianceFrameworkItemInput.Input, and is useful for accessing the field via an interface. +func (v *__DetachComplianceLibraryItemFromComplianceFrameworkItemInput) GetInput() DetachComplianceLibraryItemFromComplianceFrameworkItemInput { + return v.Input +} + // __GetComplianceFrameworkByIdInput is used internally by genqlient type __GetComplianceFrameworkByIdInput struct { FrameworkId string `json:"frameworkId"` @@ -1801,6 +2010,14 @@ type __GetComplianceFrameworkItemByIdInput struct { // GetId returns __GetComplianceFrameworkItemByIdInput.Id, and is useful for accessing the field via an interface. func (v *__GetComplianceFrameworkItemByIdInput) GetId() string { return v.Id } +// __GetComplianceFrameworkItemRelationshipsByIdInput is used internally by genqlient +type __GetComplianceFrameworkItemRelationshipsByIdInput struct { + Id string `json:"id"` +} + +// GetId returns __GetComplianceFrameworkItemRelationshipsByIdInput.Id, and is useful for accessing the field via an interface. +func (v *__GetComplianceFrameworkItemRelationshipsByIdInput) GetId() string { return v.Id } + // __GetComplianceGroupsInput is used internally by genqlient type __GetComplianceGroupsInput struct { FrameworkId string `json:"frameworkId"` @@ -1869,6 +2086,16 @@ func (v *__UpdateComplianceLibraryItemInput) GetInput() UpdateComplianceLibraryI return v.Input } +// __UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput is used internally by genqlient +type __UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput struct { + Input UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput `json:"input"` +} + +// GetInput returns __UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput.Input, and is useful for accessing the field via an interface. +func (v *__UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput) GetInput() UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput { + return v.Input +} + // __UpdateInlineQuestionRuleInstanceInput is used internally by genqlient type __UpdateInlineQuestionRuleInstanceInput struct { Instance UpdateInlineQuestionRuleInstanceInput `json:"instance"` @@ -1901,6 +2128,38 @@ func (v *__UpdateReferencedQuestionRuleInstanceInput) GetInstance() UpdateRefere return v.Instance } +func AttachComplianceLibraryItemToComplianceFrameworkItem( + ctx context.Context, + client graphql.Client, + input AttachComplianceLibraryItemToComplianceFrameworkItemInput, +) (*AttachComplianceLibraryItemToComplianceFrameworkItemResponse, error) { + req := &graphql.Request{ + OpName: "AttachComplianceLibraryItemToComplianceFrameworkItem", + Query: ` +mutation AttachComplianceLibraryItemToComplianceFrameworkItem ($input: AttachComplianceLibraryItemToComplianceFrameworkItemInput!) { + attachComplianceLibraryItemToComplianceFrameworkItem(input: $input) { + relationshipType + } +} +`, + Variables: &__AttachComplianceLibraryItemToComplianceFrameworkItemInput{ + Input: input, + }, + } + var err error + + var data AttachComplianceLibraryItemToComplianceFrameworkItemResponse + resp := &graphql.Response{Data: &data} + + err = client.MakeRequest( + ctx, + req, + resp, + ) + + return &data, err +} + func CreateComplianceFramework( ctx context.Context, client graphql.Client, @@ -2329,6 +2588,38 @@ mutation DeleteRuleInstance ($id: ID!) { return &data, err } +func DetachComplianceLibraryItemFromComplianceFrameworkItem( + ctx context.Context, + client graphql.Client, + input DetachComplianceLibraryItemFromComplianceFrameworkItemInput, +) (*DetachComplianceLibraryItemFromComplianceFrameworkItemResponse, error) { + req := &graphql.Request{ + OpName: "DetachComplianceLibraryItemFromComplianceFrameworkItem", + Query: ` +mutation DetachComplianceLibraryItemFromComplianceFrameworkItem ($input: DetachComplianceLibraryItemFromComplianceFrameworkItemInput!) { + detachComplianceLibraryItemFromComplianceFrameworkItem(input: $input) { + relationshipType + } +} +`, + Variables: &__DetachComplianceLibraryItemFromComplianceFrameworkItemInput{ + Input: input, + }, + } + var err error + + var data DetachComplianceLibraryItemFromComplianceFrameworkItemResponse + resp := &graphql.Response{Data: &data} + + err = client.MakeRequest( + ctx, + req, + resp, + ) + + return &data, err +} + func GetComplianceFrameworkById( ctx context.Context, client graphql.Client, @@ -2410,6 +2701,46 @@ query GetComplianceFrameworkItemById ($id: ID!) { return &data, err } +// this query for for use in compliance_relationship resource reads +func GetComplianceFrameworkItemRelationshipsById( + ctx context.Context, + client graphql.Client, + id string, +) (*GetComplianceFrameworkItemRelationshipsByIdResponse, error) { + req := &graphql.Request{ + OpName: "GetComplianceFrameworkItemRelationshipsById", + Query: ` +query GetComplianceFrameworkItemRelationshipsById ($id: ID!) { + complianceFrameworkItem(input: {id:$id}) { + libraryItems { + inheritedEvidenceLibraryItems { + id + } + ignoredEvidenceLibraryItems { + id + } + } + } +} +`, + Variables: &__GetComplianceFrameworkItemRelationshipsByIdInput{ + Id: id, + }, + } + var err error + + var data GetComplianceFrameworkItemRelationshipsByIdResponse + resp := &graphql.Response{Data: &data} + + err = client.MakeRequest( + ctx, + req, + resp, + ) + + return &data, err +} + // FIXME: there is currently no `complianceGroup` query, so the full list // must be retrieved and then searched for the matching ID func GetComplianceGroups( @@ -2722,6 +3053,38 @@ mutation UpdateComplianceLibraryItem ($input: UpdateComplianceLibraryItemInput!) return &data, err } +func UpdateComplianceLibraryItemToComplianceFrameworkItemRelationship( + ctx context.Context, + client graphql.Client, + input UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput, +) (*UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipResponse, error) { + req := &graphql.Request{ + OpName: "UpdateComplianceLibraryItemToComplianceFrameworkItemRelationship", + Query: ` +mutation UpdateComplianceLibraryItemToComplianceFrameworkItemRelationship ($input: UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput!) { + updateComplianceLibraryItemToComplianceFrameworkItemRelationship(input: $input) { + relationshipType + } +} +`, + Variables: &__UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput{ + Input: input, + }, + } + var err error + + var data UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipResponse + resp := &graphql.Response{Data: &data} + + err = client.MakeRequest( + ctx, + req, + resp, + ) + + return &data, err +} + // The API is inconsistent about empty values, so `omitempty` is required // for some of these. For example "when: null," will produce an error, but // `templates: null` will not when include in the request. diff --git a/jupiterone/modifiers.go b/jupiterone/modifiers.go index 3254d95c..01c14925 100644 --- a/jupiterone/modifiers.go +++ b/jupiterone/modifiers.go @@ -11,6 +11,9 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) +// UUIDStrLength is the length of UUID. TODO: Add a UUID validator +const UUIDStrLength = 36 + var _ planmodifier.String = (*jsonIgnoreDiff)(nil) var _ planmodifier.List = (*jsonIgnoreDiff)(nil) var _ planmodifier.Map = (*jsonIgnoreDiff)(nil) diff --git a/jupiterone/provider.go b/jupiterone/provider.go index bab1b309..7404b0f7 100644 --- a/jupiterone/provider.go +++ b/jupiterone/provider.go @@ -135,6 +135,7 @@ func (*JupiterOneProvider) Resources(context.Context) []func() resource.Resource NewGroupResource, NewFrameworkItemResource, NewLibraryItemResource, + NewComplianceRelationshipResource, } } diff --git a/jupiterone/resource_compliance_relationship.go b/jupiterone/resource_compliance_relationship.go new file mode 100644 index 00000000..11c0980a --- /dev/null +++ b/jupiterone/resource_compliance_relationship.go @@ -0,0 +1,233 @@ +package jupiterone + +import ( + "context" + "fmt" + + "github.com/Khan/genqlient/graphql" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/jupiterone/terraform-provider-jupiterone/jupiterone/internal/client" +) + +var ComplianceRelationshipTypes = []string{ + string(client.LibraryItemToFrameworkItemRelationshipTypeIgnoreEvidence), + string(client.LibraryItemToFrameworkItemRelationshipTypeInheritEvidence), +} + +type ComplianceRelationshipModel struct { + Id types.String `tfsdk:"id"` + LibraryItemId types.String `tfsdk:"library_item_id"` + FrameworkItemId types.String `tfsdk:"framework_item_id"` + RelationshipType types.String `tfsdk:"relationship_type"` +} + +type ComplianceRelationshipResource struct { + version string + qlient graphql.Client +} + +var _ resource.Resource = &ComplianceRelationshipResource{} +var _ resource.ResourceWithConfigure = &ComplianceRelationshipResource{} + +func NewComplianceRelationshipResource() resource.Resource { + return &ComplianceRelationshipResource{} +} + +// Metadata implements resource.Resource. +func (*ComplianceRelationshipResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_compliance_relationship" +} + +func (r *ComplianceRelationshipResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + p, ok := req.ProviderData.(*JupiterOneProvider) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected JupiterOneProvider, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.version = p.version + r.qlient = p.Qlient +} + +func (*ComplianceRelationshipResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "A relationship between a framework item (requirement) and a library item (control).", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "library_item_id": schema.StringAttribute{ + Required: true, + Description: "The id of the library item (control) to link", + Validators: []validator.String{ + stringvalidator.LengthBetween(UUIDStrLength, UUIDStrLength), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "framework_item_id": schema.StringAttribute{ + Required: true, + Description: "The id of the framework item (requirement) to link", + Validators: []validator.String{ + stringvalidator.LengthBetween(UUIDStrLength, UUIDStrLength), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "relationship_type": schema.StringAttribute{ + Required: true, + Description: "Whether to INHERIT_EVIDENCE or IGNORE_EVIDENCE in the linked framework item", + Validators: []validator.String{ + stringvalidator.OneOf(ComplianceRelationshipTypes...), + }, + }, + }, + } +} + +// Create implements resource.Resource. +func (r *ComplianceRelationshipResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *ComplianceRelationshipModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + _, err := client.AttachComplianceLibraryItemToComplianceFrameworkItem(ctx, r.qlient, client.AttachComplianceLibraryItemToComplianceFrameworkItemInput{ + LibraryItemId: data.LibraryItemId.ValueString(), + FrameworkItemId: data.FrameworkItemId.ValueString(), + RelationshipType: client.LibraryItemToFrameworkItemRelationshipType(data.RelationshipType.ValueString()), + }) + + if err != nil { + resp.Diagnostics.AddError("failed to create compliance relationship", err.Error()) + return + } + + data.Id = types.StringValue(fmt.Sprintf("%s-%s", data.LibraryItemId.ValueString(), data.FrameworkItemId.ValueString())) + + tflog.Trace(ctx, "Attached compliance relationship", + map[string]interface{}{"library_item_id": data.LibraryItemId, "framework_item_id": data.FrameworkItemId}) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +// Delete implements resource.Resource. +func (r *ComplianceRelationshipResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data ComplianceRelationshipModel + + // Read Terraform ste into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + _, err := client.DetachComplianceLibraryItemFromComplianceFrameworkItem(ctx, r.qlient, client.DetachComplianceLibraryItemFromComplianceFrameworkItemInput{ + LibraryItemId: data.LibraryItemId.ValueString(), + FrameworkItemId: data.FrameworkItemId.ValueString(), + }) + + if err != nil { + resp.Diagnostics.AddError("failed to delete compliance relationship", err.Error()) + return + } + + data.Id = types.StringValue(fmt.Sprintf("%s-%s", data.LibraryItemId.ValueString(), data.FrameworkItemId.ValueString())) + + tflog.Trace(ctx, "Detached compliance relationship", + map[string]interface{}{"library_item_id": data.LibraryItemId, "framework_item_id": data.FrameworkItemId}) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +// Read implements resource.Resource. +func (r *ComplianceRelationshipResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data ComplianceRelationshipModel + + // Read Terraform ste into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + var i client.GetComplianceFrameworkItemRelationshipsByIdComplianceFrameworkItem + if r, err := client.GetComplianceFrameworkItemRelationshipsById(ctx, r.qlient, data.FrameworkItemId.ValueString()); err != nil { + resp.Diagnostics.AddError("failed to find framework item", err.Error()) + return + } else { + i = r.ComplianceFrameworkItem + } + + data.RelationshipType = types.StringUnknown() + for _, item := range i.LibraryItems.InheritedEvidenceLibraryItems { + if item.Id == data.LibraryItemId.ValueString() { + data.RelationshipType = types.StringValue(string(client.LibraryItemToFrameworkItemRelationshipTypeInheritEvidence)) + break + } + } + for _, item := range i.LibraryItems.IgnoredEvidenceLibraryItems { + if item.Id == data.LibraryItemId.ValueString() { + data.RelationshipType = types.StringValue(string(client.LibraryItemToFrameworkItemRelationshipTypeIgnoreEvidence)) + break + } + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +// Update implements resource.Resource. +func (r *ComplianceRelationshipResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *ComplianceRelationshipModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + _, err := client.UpdateComplianceLibraryItemToComplianceFrameworkItemRelationship(ctx, r.qlient, client.UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipInput{ + FrameworkItemId: data.FrameworkItemId.ValueString(), + LibraryItemId: data.LibraryItemId.ValueString(), + Updates: client.UpdateComplianceLibraryItemToComplianceFrameworkItemRelationshipFields{ + RelationshipType: client.LibraryItemToFrameworkItemRelationshipType(data.RelationshipType.ValueString()), + }, + }) + + if err != nil { + resp.Diagnostics.AddError("failed to create framework", err.Error()) + return + } + + data.Id = types.StringValue(fmt.Sprintf("%s-%s", data.LibraryItemId.ValueString(), data.FrameworkItemId.ValueString())) + + tflog.Trace(ctx, "Updated compliance relationship", + map[string]interface{}{"library_item_id": data.LibraryItemId, "framework_item_id": data.FrameworkItemId}) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/jupiterone/resource_compliance_relationship_test.go b/jupiterone/resource_compliance_relationship_test.go new file mode 100644 index 00000000..1f7711d1 --- /dev/null +++ b/jupiterone/resource_compliance_relationship_test.go @@ -0,0 +1,221 @@ +package jupiterone + +import ( + "context" + "fmt" + "strings" + "testing" + "time" + + "github.com/Khan/genqlient/graphql" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/jupiterone/terraform-provider-jupiterone/jupiterone/internal/client" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func testRelationshipConfig(frameworkItemId, libraryItemId string, relationship client.LibraryItemToFrameworkItemRelationshipType) string { + return fmt.Sprintf(` + resource jupiterone_compliance_relationship tf_acc_link_test { + framework_item_id = %q + library_item_id = %q + relationship_type = %q + } + `, frameworkItemId, libraryItemId, relationship) +} + +func TestComplianceRelationship_Basic(t *testing.T) { + ctx := context.TODO() + + recordingClient, directClient, cleanup := setupTestClients(ctx, t) + defer cleanup(t) + + // Because the library item is independent of the of the framework and + // child resources, terraform may perform the creations out of order, so + // creating them all in Steps.Config elements can be unpredictable. + // Necessary fixtures must be created before the tests and since the + // generated UUIDs are necessary, this must use the recordingClient. + frameworkId, frameworkItemId, libraryItemId, err := createTestRelationshipFixture(ctx, recordingClient) + require.NoError(t, err, "error creating test resources for compliancerelationship, resources may need to manually removed") + defer func() { + err := deleteTestRelationshipFixture(ctx, recordingClient, frameworkId, libraryItemId) + assert.NoError(t, err, "error removing test resources for compliancerelationship, resources may need to manually removed") + }() + + testRelationshipResourceName := "jupiterone_compliance_relationship.tf_acc_link_test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(recordingClient), + CheckDestroy: testAccCheckRelationshipDestroy(ctx, directClient, frameworkItemId, libraryItemId), + Steps: []resource.TestStep{ + { + Config: testRelationshipConfig(frameworkItemId, libraryItemId, client.LibraryItemToFrameworkItemRelationshipTypeInheritEvidence), + Check: resource.ComposeTestCheckFunc( + testAccCheckRelationshipExists(ctx, directClient, frameworkItemId, libraryItemId, string(client.LibraryItemToFrameworkItemRelationshipTypeInheritEvidence)), + resource.TestCheckResourceAttrSet(testRelationshipResourceName, "id"), + resource.TestCheckResourceAttr(testRelationshipResourceName, "framework_item_id", frameworkItemId), + resource.TestCheckResourceAttr(testRelationshipResourceName, "library_item_id", libraryItemId), + resource.TestCheckResourceAttr(testRelationshipResourceName, "relationship_type", string(client.LibraryItemToFrameworkItemRelationshipTypeInheritEvidence)), + ), + }, + { + Config: testRelationshipConfig(frameworkItemId, libraryItemId, client.LibraryItemToFrameworkItemRelationshipTypeIgnoreEvidence), + Check: resource.ComposeTestCheckFunc( + testAccCheckRelationshipExists(ctx, directClient, frameworkItemId, libraryItemId, string(client.LibraryItemToFrameworkItemRelationshipTypeIgnoreEvidence)), + resource.TestCheckResourceAttrSet(testRelationshipResourceName, "id"), + resource.TestCheckResourceAttr(testRelationshipResourceName, "framework_item_id", frameworkItemId), + resource.TestCheckResourceAttr(testRelationshipResourceName, "library_item_id", libraryItemId), + resource.TestCheckResourceAttr(testRelationshipResourceName, "relationship_type", string(client.LibraryItemToFrameworkItemRelationshipTypeIgnoreEvidence)), + ), + }, + }, + }) +} + +func testAccCheckRelationshipExists(ctx context.Context, qlient graphql.Client, frameworkItemId, libraryItemId, relType string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if qlient == nil { + return nil + } + + duration := 10 * time.Second + for _, r := range s.RootModule().Resources { + if r.Type != "jupiterone_compliancerelationship" { + continue + } + err := resource.RetryContext(ctx, duration, func() *resource.RetryError { + + resp, err := client.GetComplianceFrameworkItemRelationshipsById(ctx, qlient, frameworkItemId) + + if err == nil { + for _, l := range resp.ComplianceFrameworkItem.LibraryItems.InheritedEvidenceLibraryItems { + if l.Id == libraryItemId { + return nil + } + } + for _, l := range resp.ComplianceFrameworkItem.LibraryItems.IgnoredEvidenceLibraryItems { + if l.Id == libraryItemId { + return nil + } + } + return resource.RetryableError(fmt.Errorf("Relationship does not exist for fraemwork item (id=%q)", frameworkItemId)) + } + + if err != nil && strings.Contains(err.Error(), "Could not find compliance relationship for framework") { + return resource.RetryableError(err) + } + + return resource.NonRetryableError(err) + }) + + if err != nil { + return err + } + } + + return nil + } +} + +func testAccCheckRelationshipDestroy(ctx context.Context, qlient graphql.Client, frameworkItemId, libraryItemId string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if qlient == nil { + return nil + } + + duration := 10 * time.Second + for _, r := range s.RootModule().Resources { + if r.Type != "jupiterone_compliancerelationship" { + continue + } + frameworkItemId := r.Primary.Attributes["framework_item_id"] + libraryItemId := r.Primary.Attributes["library_item_id"] + err := resource.RetryContext(ctx, duration, func() *resource.RetryError { + + resp, err := client.GetComplianceFrameworkItemRelationshipsById(ctx, qlient, frameworkItemId) + + if err == nil { + for _, l := range resp.ComplianceFrameworkItem.LibraryItems.InheritedEvidenceLibraryItems { + if l.Id == libraryItemId { + return resource.RetryableError(fmt.Errorf("Relationships still exists for framework item (id=%q)", frameworkItemId)) + } + } + for _, l := range resp.ComplianceFrameworkItem.LibraryItems.IgnoredEvidenceLibraryItems { + if l.Id == libraryItemId { + return resource.RetryableError(fmt.Errorf("Relationships still exists for framework item (id=%q)", frameworkItemId)) + } + } + return nil + } + + if err != nil && strings.Contains(err.Error(), "Could not find") { + return nil + } + + return resource.NonRetryableError(err) + }) + + if err != nil { + return err + } + } + + return nil + } +} + +func deleteTestRelationshipFixture(ctx context.Context, qlient graphql.Client, frameworkId, libraryItemId string) error { + _, err := client.DeleteComplianceFramework(ctx, qlient, client.DeleteComplianceFrameworkInput{Id: frameworkId}) + if err != nil { + return err + + } + + _, err = client.DeleteComplianceLibraryItem(ctx, qlient, libraryItemId) + return err +} + +func createTestRelationshipFixture(ctx context.Context, qlient graphql.Client) (frameworkId, frameworkItemId, libraryItemId string, err error) { + var f *client.CreateComplianceFrameworkResponse + f, err = client.CreateComplianceFramework(ctx, qlient, client.CreateComplianceFrameworkInput{ + Name: "tf-acc-test-framework", + Version: "1", + FrameworkType: client.ComplianceFrameworkTypeStandard, + }) + if err != nil { + return + } + frameworkId = f.CreateComplianceFramework.Id + + var g *client.CreateComplianceGroupResponse + g, err = client.CreateComplianceGroup(ctx, qlient, client.CreateComplianceGroupInput{ + Name: "tf-acc-test-group", + FrameworkId: frameworkId, + }) + if err != nil { + return + } + + var i *client.CreateComplianceFrameworkItemResponse + i, err = client.CreateComplianceFrameworkItem(ctx, qlient, client.CreateComplianceFrameworkItemInput{ + Name: "tf-acc-test-item", + FrameworkId: frameworkId, + GroupId: g.CreateComplianceGroup.Id, + }) + if err != nil { + return + } + frameworkItemId = i.CreateComplianceFrameworkItem.Id + + var l *client.CreateComplianceLibraryItemResponse + l, err = client.CreateComplianceLibraryItem(ctx, qlient, client.CreateComplianceLibraryItemInput{ + Name: "tf-acc-test-control", + }) + if err != nil { + return + } + libraryItemId = l.CreateComplianceLibraryItem.Id + + return frameworkId, frameworkItemId, libraryItemId, nil +} diff --git a/jupiterone/resource_group.go b/jupiterone/resource_group.go index 35242e33..9393dabb 100644 --- a/jupiterone/resource_group.go +++ b/jupiterone/resource_group.go @@ -6,11 +6,13 @@ import ( "fmt" "github.com/Khan/genqlient/graphql" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/jupiterone/terraform-provider-jupiterone/jupiterone/internal/client" @@ -81,6 +83,9 @@ Refer to the resource_framework docs for example usage`, "framework_id": schema.StringAttribute{ Required: true, Description: "The internal ID of the framework this group is a part of", + Validators: []validator.String{ + stringvalidator.LengthBetween(UUIDStrLength, UUIDStrLength), + }, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), },