-
Notifications
You must be signed in to change notification settings - Fork 330
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Security defenses browser headers #130
Changes from all commits
499bc68
44bb250
311025b
6ea7a1b
233da29
0a8765e
e6ad60b
147b4ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -255,6 +255,61 @@ func resourceKeycloakRealm() *schema.Resource { | |
}, | ||
}, | ||
}, | ||
|
||
//Security Defenses | ||
"security_defenses": { | ||
Type: schema.TypeList, | ||
Optional: true, | ||
MaxItems: 1, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"headers": { | ||
Type: schema.TypeList, | ||
Optional: true, | ||
MaxItems: 1, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"x_frame_options": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Default: "SAMEORIGIN", | ||
}, | ||
"content_security_policy": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Default: "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", | ||
}, | ||
"content_security_policy_report_only": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Default: "", | ||
}, | ||
"x_content_type_options": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Default: "nosniff", | ||
}, | ||
"x_robots_tag": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Default: "none", | ||
}, | ||
"x_xss_protection": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Default: "1; mode=block", | ||
}, | ||
"strict_transport_security": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Default: "max-age=31536000; includeSubDomains", | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
@@ -456,9 +511,44 @@ func getRealmFromData(data *schema.ResourceData) (*keycloak.Realm, error) { | |
realm.ActionTokenGeneratedByAdminLifespan = actionTokenGeneratedByAdminLifespanDurationString | ||
} | ||
|
||
//security defenses | ||
if v, ok := data.GetOk("security_defenses"); ok { | ||
securityDefensesSettings := v.([]interface{})[0].(map[string]interface{}) | ||
|
||
headersConfig := securityDefensesSettings["headers"].([]interface{}) | ||
if len(headersConfig) == 1 { | ||
headerSettings := headersConfig[0].(map[string]interface{}) | ||
|
||
realm.Attributes = keycloak.Attributes{ | ||
BrowserHeaderContentSecurityPolicy: headerSettings["content_security_policy"].(string), | ||
BrowserHeaderContentSecurityPolicyReportOnly: headerSettings["content_security_policy_report_only"].(string), | ||
BrowserHeaderStrictTransportSecurity: headerSettings["strict_transport_security"].(string), | ||
BrowserHeaderXContentTypeOptions: headerSettings["x_content_type_options"].(string), | ||
BrowserHeaderXFrameOptions: headerSettings["x_frame_options"].(string), | ||
BrowserHeaderXRobotsTag: headerSettings["x_robots_tag"].(string), | ||
BrowserHeaderXXSSProtection: headerSettings["x_xss_protection"].(string), | ||
} | ||
} else { | ||
setDefaultSecuritySettings(realm) | ||
} | ||
} else { | ||
setDefaultSecuritySettings(realm) | ||
} | ||
return realm, nil | ||
} | ||
|
||
func setDefaultSecuritySettings(realm *keycloak.Realm) { | ||
realm.Attributes = keycloak.Attributes{ | ||
BrowserHeaderContentSecurityPolicy: "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", | ||
BrowserHeaderContentSecurityPolicyReportOnly: "", | ||
BrowserHeaderStrictTransportSecurity: "max-age=31536000; includeSubDomains", | ||
BrowserHeaderXContentTypeOptions: "nosniff", | ||
BrowserHeaderXFrameOptions: "SAMEORIGIN", | ||
BrowserHeaderXRobotsTag: "none", | ||
BrowserHeaderXXSSProtection: "1; mode=block", | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand the purpose of this function if the schema is already setting these defaults, could you clear this up for me? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem is, when you remove the 'headers' block in terraform, the values that you had set before, will persist and will not be reset to the keycloak defaults. Furthermore the default values set in scheme, will only be used when there is a 'headers' block. This way, users only need to fill in headers to are different from the default. example
Terraform will perform the following actions:
But in Keycloak this will remain on "DENY" with the setDefaultSecuritySettings(realm) method, this works as intended. also see test TestAccKeycloakRealm_securityDefenses I agree this feels like a workaround/patch solution, But I did not see any other solution. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I'm torn on this one. As a user, would you expect that deleting an attribute would cause a change to be made within Keycloak, or for that attribute to simply not be managed by Terraform anymore? I think I'm in favor of the latter. Most of my Terraform experience is with the Google provider, and a lot of their resources behave this way - if an optional field is deleted, it doesn't change it back to the perceived default, it simply stops caring about it. I understand the point you're making, I'm just not sure what kind of behavior a typical Terraform user would expect here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I dont agree on the latter.. If you delete an attribute from terraform it will delete or change that managed resource, it is only when you remove the current state of that resource & delete the attribute from the terraform code that it is "forgotten" or no longer managed. So as a typical terraform user removing something from my terraform codebase I do expect changes.. |
||
|
||
func setRealmData(data *schema.ResourceData, realm *keycloak.Realm) { | ||
data.SetId(realm.Realm) | ||
|
||
|
@@ -534,6 +624,29 @@ func setRealmData(data *schema.ResourceData, realm *keycloak.Realm) { | |
} else { | ||
data.Set("internationalization", nil) | ||
} | ||
|
||
if _, ok := data.GetOk("security_defenses"); ok { | ||
|
||
if (keycloak.Attributes{}) == realm.Attributes { | ||
data.Set("security_defenses", nil) | ||
} else { | ||
securityDefensesSettings := make(map[string]interface{}) | ||
|
||
headersSettings := make(map[string]interface{}) | ||
|
||
headersSettings["content_security_policy"] = realm.Attributes.BrowserHeaderContentSecurityPolicy | ||
headersSettings["content_security_policy_report_only"] = realm.Attributes.BrowserHeaderContentSecurityPolicyReportOnly | ||
headersSettings["strict_transport_security"] = realm.Attributes.BrowserHeaderStrictTransportSecurity | ||
headersSettings["x_content_type_options"] = realm.Attributes.BrowserHeaderXContentTypeOptions | ||
headersSettings["x_frame_options"] = realm.Attributes.BrowserHeaderXFrameOptions | ||
headersSettings["x_robots_tag"] = realm.Attributes.BrowserHeaderXRobotsTag | ||
headersSettings["x_xss_protection"] = realm.Attributes.BrowserHeaderXXSSProtection | ||
|
||
securityDefensesSettings["headers"] = []interface{}{headersSettings} | ||
|
||
data.Set("security_defenses", []interface{}{securityDefensesSettings}) | ||
} | ||
} | ||
} | ||
|
||
func resourceKeycloakRealmCreate(data *schema.ResourceData, meta interface{}) error { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be setting these defaults manually, or relying on Keycloak to do this for us? I'm worried that there is a chance that upgrading your Keycloak instance could change the recommended defaults, but the provider would still set the old values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This allows users to only fill in the fields where they want to diverge from the default keycloak value. If there is no default value in the scheme, we would need to make every field required. This would mean that the user would need to go and look in keycloak to copy the values of the fields they do not want to change.
If we put the default on empty string, an empty string will passed to keycloak, and imho that is also not a desired result for the users.
Even if keycloak would change the recommended defaults in the future, these current default values will be better than no value, or a value that the user would have copied from the keycloak gui.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that we would want sensible defaults to be set if the user omits any of these attributes, but what I'm suggesting is to let Keycloak handle that instead of hardcoding the defaults (which may change in the future) within the provider.
If a user doesn't specify one of these attributes, we don't have to send Keycloak an empty string - we can use
omitempty
within the JSON definition for the struct to tell the provider to not include properties with empty string values when sending the request to Keycloak. This will allow Keycloak to set the default to whatever it wants.What are your thoughts on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can remove the defaults.
But Imho I prefer to have the defaults, because of following example without the defaults:
lets focus on the x_robots_tag field.
User creates following config:
in keycloak x_robots_tag has now the value none
user changes the config to:
in keycloak x_robots_tag has now the value bla
user removes the value again
in keycloak x_robots_tag has now the value ""
because
(could this be solved by checking in getRealmFromData if x_robots_tag is present?)
In the end this heavily depends on what you decide on the comment/discussion below. Because If we expect that the plugin restores the default values, we need the default values in the scheme. If not, we can remove the defaults.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I'm fine with keeping what you currently have implemented.