Skip to content

Commit 68bc3fb

Browse files
authored
feat: Project resource supports client security settings (#537)
1 parent 275d5dc commit 68bc3fb

File tree

6 files changed

+473
-43
lines changed

6 files changed

+473
-43
lines changed

GNUmakefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ fmt:
2525

2626
.PHONY: test
2727
test:
28-
go test -v -cover -timeout=120s -parallel=10 ./...
28+
go test ./... -v -cover -timeout=120s -parallel=10 $(TESTARGS)
2929

3030
.PHONY: testacc
3131
testacc:
32-
TF_ACC=1 go test -v -cover -timeout 120m ./...
32+
TF_ACC=1 go test ./... -v -cover -timeout 120m $(TESTARGS)
3333

3434
.PHONY: sweep
3535
sweep:

docs/resources/project.md

+13
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ resource "sentry_project" "default" {
5858

5959
### Optional
6060

61+
- `client_security` (Attributes) Configure origin URLs which Sentry should accept events from. This is used for communication with clients like [sentry-javascript](https://github.com/getsentry/sentry-javascript). (see [below for nested schema](#nestedatt--client_security))
6162
- `default_key` (Boolean) Whether to create a default key. By default, Sentry will create a key for you. If you wish to manage keys manually, set this to false and create keys using the `sentry_key` resource.
6263
- `default_rules` (Boolean) Whether to create a default issue alert. Defaults to true where the behavior is to alert the user on every new issue.
6364
- `digests_max_delay` (Number) The maximum amount of time (in seconds) to wait between scheduling digests for delivery.
@@ -75,6 +76,18 @@ resource "sentry_project" "default" {
7576
- `id` (String) The ID of this resource.
7677
- `internal_id` (String) The internal ID for this project.
7778

79+
<a id="nestedatt--client_security"></a>
80+
### Nested Schema for `client_security`
81+
82+
Optional:
83+
84+
- `allowed_domains` (Set of String) A list of allowed domains. Examples: https://example.com, *, *.example.com, *:80.
85+
- `scrape_javascript` (Boolean) Enable JavaScript source fetching. Allow Sentry to scrape missing JavaScript source context when possible.
86+
- `security_token` (String) Security Token. Outbound requests matching Allowed Domains will have the header "{security_token_header}: {security_token}" appended.
87+
- `security_token_header` (String) Security Token Header. Outbound requests matching Allowed Domains will have the header "{security_token_header}: {security_token}" appended.
88+
- `verify_tls_ssl` (Boolean) Verify TLS/SSL. Outbound requests will verify TLS (sometimes known as SSL) connections.
89+
90+
7891
<a id="nestedatt--filters"></a>
7992
### Nested Schema for `filters`
8093

internal/apiclient/api.yaml

+30
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,18 @@ paths:
113113
type: string
114114
options:
115115
type: object
116+
allowedDomains:
117+
type: array
118+
items:
119+
type: string
120+
scrapeJavaScript:
121+
type: boolean
122+
securityToken:
123+
type: string
124+
securityTokenHeader:
125+
type: string
126+
verifySSL:
127+
type: boolean
116128
responses:
117129
"200":
118130
description: OK
@@ -407,6 +419,11 @@ components:
407419
- resolveAge
408420
- fingerprintingRules
409421
- groupingEnhancements
422+
- allowedDomains
423+
- scrapeJavaScript
424+
- securityToken
425+
- securityTokenHeader
426+
- verifySSL
410427
properties:
411428
organization:
412429
$ref: "#/components/schemas/Organization"
@@ -449,6 +466,19 @@ components:
449466
type: string
450467
groupingEnhancements:
451468
type: string
469+
allowedDomains:
470+
type: array
471+
items:
472+
type: string
473+
scrapeJavaScript:
474+
type: boolean
475+
securityToken:
476+
type: string
477+
securityTokenHeader:
478+
type: string
479+
nullable: true
480+
verifySSL:
481+
type: boolean
452482
ProjectKey:
453483
type: object
454484
required:

internal/apiclient/apiclient.gen.go

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/provider/resource_project.go

+164
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,42 @@ func (m *ProjectFilterResourceModel) Fill(ctx context.Context, project apiclient
9393
return
9494
}
9595

96+
type ProjectClientSecurityResourceModel struct {
97+
AllowedDomains types.Set `tfsdk:"allowed_domains"`
98+
ScrapeJavascript types.Bool `tfsdk:"scrape_javascript"`
99+
SecurityToken types.String `tfsdk:"security_token"`
100+
SecurityTokenHeader types.String `tfsdk:"security_token_header"`
101+
VerifyTlsSsl types.Bool `tfsdk:"verify_tls_ssl"`
102+
}
103+
104+
func (m ProjectClientSecurityResourceModel) AttributeTypes() map[string]attr.Type {
105+
return map[string]attr.Type{
106+
"allowed_domains": types.SetType{ElemType: types.StringType},
107+
"scrape_javascript": types.BoolType,
108+
"security_token": types.StringType,
109+
"security_token_header": types.StringType,
110+
"verify_tls_ssl": types.BoolType,
111+
}
112+
}
113+
114+
func (m *ProjectClientSecurityResourceModel) Fill(ctx context.Context, project apiclient.Project) (diags diag.Diagnostics) {
115+
m.AllowedDomains = types.SetValueMust(types.StringType, sliceutils.Map(func(v string) attr.Value {
116+
return types.StringValue(v)
117+
}, project.AllowedDomains))
118+
m.ScrapeJavascript = types.BoolValue(project.ScrapeJavaScript)
119+
m.SecurityToken = types.StringValue(project.SecurityToken)
120+
121+
if project.SecurityTokenHeader == nil {
122+
m.SecurityTokenHeader = types.StringValue("")
123+
} else {
124+
m.SecurityTokenHeader = types.StringPointerValue(project.SecurityTokenHeader)
125+
}
126+
127+
m.VerifyTlsSsl = types.BoolValue(project.VerifySSL)
128+
129+
return
130+
}
131+
96132
type ProjectResourceModel struct {
97133
Id types.String `tfsdk:"id"`
98134
Organization types.String `tfsdk:"organization"`
@@ -110,6 +146,7 @@ type ProjectResourceModel struct {
110146
Filters types.Object `tfsdk:"filters"`
111147
FingerprintingRules sentrytypes.TrimmedString `tfsdk:"fingerprinting_rules"`
112148
GroupingEnhancements sentrytypes.TrimmedString `tfsdk:"grouping_enhancements"`
149+
ClientSecurity types.Object `tfsdk:"client_security"`
113150
}
114151

115152
func (m *ProjectResourceModel) Fill(ctx context.Context, project apiclient.Project) (diags diag.Diagnostics) {
@@ -146,6 +183,13 @@ func (m *ProjectResourceModel) Fill(ctx context.Context, project apiclient.Proje
146183
m.FingerprintingRules = sentrytypes.TrimmedStringValue(project.FingerprintingRules)
147184
m.GroupingEnhancements = sentrytypes.TrimmedStringValue(project.GroupingEnhancements)
148185

186+
var clientSecurity ProjectClientSecurityResourceModel
187+
diags.Append(clientSecurity.Fill(ctx, project)...)
188+
189+
var clientSecurityDiags diag.Diagnostics
190+
m.ClientSecurity, clientSecurityDiags = types.ObjectValueFrom(ctx, clientSecurity.AttributeTypes(), clientSecurity)
191+
diags.Append(clientSecurityDiags...)
192+
149193
return
150194
}
151195

@@ -315,6 +359,63 @@ func (r *ProjectResource) Schema(ctx context.Context, req resource.SchemaRequest
315359
stringplanmodifier.UseStateForUnknown(),
316360
},
317361
},
362+
"client_security": schema.SingleNestedAttribute{
363+
MarkdownDescription: "Configure origin URLs which Sentry should accept events from. This is used for communication with clients like [sentry-javascript](https://github.com/getsentry/sentry-javascript).",
364+
Optional: true,
365+
Computed: true,
366+
Attributes: map[string]schema.Attribute{
367+
"allowed_domains": schema.SetAttribute{
368+
MarkdownDescription: "A list of allowed domains. Examples: https://example.com, *, *.example.com, *:80.",
369+
ElementType: types.StringType,
370+
Optional: true,
371+
Computed: true,
372+
Validators: []validator.Set{
373+
setvalidator.SizeAtLeast(1),
374+
},
375+
PlanModifiers: []planmodifier.Set{
376+
setplanmodifier.UseStateForUnknown(),
377+
},
378+
},
379+
"scrape_javascript": schema.BoolAttribute{
380+
MarkdownDescription: "Enable JavaScript source fetching. Allow Sentry to scrape missing JavaScript source context when possible.",
381+
Optional: true,
382+
Computed: true,
383+
PlanModifiers: []planmodifier.Bool{
384+
boolplanmodifier.UseStateForUnknown(),
385+
},
386+
},
387+
"security_token": schema.StringAttribute{
388+
MarkdownDescription: "Security Token. Outbound requests matching Allowed Domains will have the header \"{security_token_header}: {security_token}\" appended.",
389+
Optional: true,
390+
Computed: true,
391+
PlanModifiers: []planmodifier.String{
392+
stringplanmodifier.UseStateForUnknown(),
393+
},
394+
},
395+
"security_token_header": schema.StringAttribute{
396+
MarkdownDescription: "Security Token Header. Outbound requests matching Allowed Domains will have the header \"{security_token_header}: {security_token}\" appended.",
397+
Optional: true,
398+
Computed: true,
399+
Validators: []validator.String{
400+
stringvalidator.LengthAtMost(20),
401+
},
402+
PlanModifiers: []planmodifier.String{
403+
stringplanmodifier.UseStateForUnknown(),
404+
},
405+
},
406+
"verify_tls_ssl": schema.BoolAttribute{
407+
MarkdownDescription: "Verify TLS/SSL. Outbound requests will verify TLS (sometimes known as SSL) connections.",
408+
Optional: true,
409+
Computed: true,
410+
PlanModifiers: []planmodifier.Bool{
411+
boolplanmodifier.UseStateForUnknown(),
412+
},
413+
},
414+
},
415+
PlanModifiers: []planmodifier.Object{
416+
objectplanmodifier.UseStateForUnknown(),
417+
},
418+
},
318419
},
319420
}
320421
}
@@ -436,6 +537,37 @@ func (r *ProjectResource) Create(ctx context.Context, req resource.CreateRequest
436537
updateBody.GroupingEnhancements = data.GroupingEnhancements.ValueStringPointer()
437538
}
438539

540+
if !data.ClientSecurity.IsUnknown() {
541+
var clientSecurity ProjectClientSecurityResourceModel
542+
resp.Diagnostics.Append(data.ClientSecurity.As(ctx, &clientSecurity, basetypes.ObjectAsOptions{})...)
543+
if resp.Diagnostics.HasError() {
544+
return
545+
}
546+
547+
if !clientSecurity.AllowedDomains.IsUnknown() {
548+
resp.Diagnostics.Append(clientSecurity.AllowedDomains.ElementsAs(ctx, &updateBody.AllowedDomains, false)...)
549+
if resp.Diagnostics.HasError() {
550+
return
551+
}
552+
}
553+
554+
if !clientSecurity.ScrapeJavascript.IsUnknown() {
555+
updateBody.ScrapeJavaScript = clientSecurity.ScrapeJavascript.ValueBoolPointer()
556+
}
557+
558+
if !clientSecurity.SecurityToken.IsUnknown() {
559+
updateBody.SecurityToken = clientSecurity.SecurityToken.ValueStringPointer()
560+
}
561+
562+
if !clientSecurity.SecurityTokenHeader.IsUnknown() {
563+
updateBody.SecurityTokenHeader = clientSecurity.SecurityTokenHeader.ValueStringPointer()
564+
}
565+
566+
if !clientSecurity.VerifyTlsSsl.IsUnknown() {
567+
updateBody.VerifySSL = clientSecurity.VerifyTlsSsl.ValueBoolPointer()
568+
}
569+
}
570+
439571
httpRespUpdate, err := r.apiClient.UpdateOrganizationProjectWithResponse(
440572
ctx,
441573
data.Organization.ValueString(),
@@ -620,6 +752,38 @@ func (r *ProjectResource) Update(ctx context.Context, req resource.UpdateRequest
620752
updateBody.GroupingEnhancements = plan.GroupingEnhancements.ValueStringPointer()
621753
}
622754

755+
if !plan.ClientSecurity.Equal(state.ClientSecurity) {
756+
var clientSecurityPlan, clientSecurityState ProjectClientSecurityResourceModel
757+
resp.Diagnostics.Append(plan.ClientSecurity.As(ctx, &clientSecurityPlan, basetypes.ObjectAsOptions{})...)
758+
resp.Diagnostics.Append(state.ClientSecurity.As(ctx, &clientSecurityState, basetypes.ObjectAsOptions{})...)
759+
if resp.Diagnostics.HasError() {
760+
return
761+
}
762+
763+
if !clientSecurityPlan.AllowedDomains.Equal(clientSecurityState.AllowedDomains) {
764+
resp.Diagnostics.Append(clientSecurityPlan.AllowedDomains.ElementsAs(ctx, &updateBody.AllowedDomains, false)...)
765+
if resp.Diagnostics.HasError() {
766+
return
767+
}
768+
}
769+
770+
if !clientSecurityPlan.ScrapeJavascript.Equal(clientSecurityState.ScrapeJavascript) {
771+
updateBody.ScrapeJavaScript = clientSecurityPlan.ScrapeJavascript.ValueBoolPointer()
772+
}
773+
774+
if !clientSecurityPlan.SecurityToken.Equal(clientSecurityState.SecurityToken) {
775+
updateBody.SecurityToken = clientSecurityPlan.SecurityToken.ValueStringPointer()
776+
}
777+
778+
if !clientSecurityPlan.SecurityTokenHeader.Equal(clientSecurityState.SecurityTokenHeader) {
779+
updateBody.SecurityTokenHeader = clientSecurityPlan.SecurityTokenHeader.ValueStringPointer()
780+
}
781+
782+
if !clientSecurityPlan.VerifyTlsSsl.Equal(clientSecurityState.VerifyTlsSsl) {
783+
updateBody.VerifySSL = clientSecurityPlan.VerifyTlsSsl.ValueBoolPointer()
784+
}
785+
}
786+
623787
httpRespUpdate, err := r.apiClient.UpdateOrganizationProjectWithResponse(
624788
ctx,
625789
plan.Organization.ValueString(),

0 commit comments

Comments
 (0)