Skip to content
This repository was archived by the owner on Aug 20, 2025. It is now read-only.

Commit c8b2999

Browse files
Merge pull request #168 from BrunoReboul/bruno/sql_maintenance_day_hour
Bruno/sql maintenance hour and exemption
2 parents a1c7406 + c8f7b7f commit c8b2999

File tree

7 files changed

+247
-24
lines changed

7 files changed

+247
-24
lines changed

policies/templates/gcp_sql_maintenance_window_v1.yaml

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,17 @@ spec:
2424
plural: gcpsqlmaintenancewindowconstraintsv1
2525
validation:
2626
openAPIV3Schema:
27-
properties: {}
27+
properties:
28+
hours:
29+
type: array
30+
items: number
31+
description: "Array of UTC hours when the maintenance window is authorised. Example night hours
32+
Optional, when not specified any hour is authorized from 0 to 23"
33+
exemptions:
34+
type: array
35+
items: string
36+
description: "Array of Cloud SQL instances to exempt from maintenance window restriction. String values in the array
37+
should correspond to the full name values of exempted Cloud SQL instances. Optional"
2838
targets:
2939
validation.gcp.forsetisecurity.org:
3040
rego: | #INLINE("validator/sql_maintenance_window.rego")
@@ -42,29 +52,56 @@ spec:
4252
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4353
# See the License for the specific language governing permissions and
4454
# limitations under the License.
45-
#
55+
#
4656
4757
package templates.gcp.GCPSQLMaintenanceWindowConstraintV1
4858
4959
import data.validator.gcp.lib as lib
5060
61+
# A violation is generated only when the rule body evaluates to true.
5162
deny[{
5263
"msg": message,
5364
"details": metadata,
5465
}] {
66+
# by default any hour accepted
67+
default_hours := {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}
68+
spec := lib.get_default(input.constraint, "spec", "")
69+
parameters := lib.get_default(spec, "parameters", "")
70+
maintenance_window_hours := lib.get_default(parameters, "hours", default_hours)
71+
desired_maintenance_window_hours := get_default_when_empty(maintenance_window_hours, default_hours)
72+
exempt_list := lib.get_default(parameters, "exemptions", [])
73+
5574
# Verify that resource is Cloud SQL instance and is not a first gen
5675
# Maintenance window is supported only on 2nd generation instances
5776
asset := input.asset
5877
asset.asset_type == "sqladmin.googleapis.com/Instance"
5978
asset.resource.data.backendType != "FIRST_GEN"
6079
61-
# get the maintenance window object
62-
maintenance_window := lib.get_default(asset.resource.data.settings, "maintenanceWindow", {})
80+
# Check if resource is in exempt list
81+
matches := {asset.name} & cast_set(exempt_list)
82+
count(matches) == 0
6383
64-
# non compliant when not found
65-
maintenance_window == {}
84+
# get instance settings
85+
settings := lib.get_default(asset.resource.data, "settings", {})
86+
instance_maintenance_window := lib.get_default(settings, "maintenanceWindow", {})
87+
instance_maintenance_window_hour := lib.get_default(instance_maintenance_window, "hour", "")
6688
67-
message := sprintf("%v missing maintenance window.", [asset.name])
89+
# check compliance
90+
hour_matches := {instance_maintenance_window_hour} & cast_set(desired_maintenance_window_hours)
91+
count(hour_matches) == 0
92+
93+
message := sprintf("%v missing or incorrect maintenance window. Hour: '%v'", [asset.name, instance_maintenance_window_hour])
6894
metadata := {"resource": asset.name}
6995
}
96+
97+
# Rule utilities
98+
get_default_when_empty(value, default_value) = output {
99+
count(value) != 0
100+
output := value
101+
}
102+
103+
get_default_when_empty(value, default_value) = output {
104+
count(value) == 0
105+
output := default_value
106+
}
70107
#ENDINLINE

validator/sql_maintenance_window.rego

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,55 @@
1212
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
15-
#
15+
#
1616

1717
package templates.gcp.GCPSQLMaintenanceWindowConstraintV1
1818

1919
import data.validator.gcp.lib as lib
2020

21+
# A violation is generated only when the rule body evaluates to true.
2122
deny[{
2223
"msg": message,
2324
"details": metadata,
2425
}] {
26+
# by default any hour accepted
27+
default_hours := {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}
28+
spec := lib.get_default(input.constraint, "spec", "")
29+
parameters := lib.get_default(spec, "parameters", "")
30+
maintenance_window_hours := lib.get_default(parameters, "hours", default_hours)
31+
desired_maintenance_window_hours := get_default_when_empty(maintenance_window_hours, default_hours)
32+
exempt_list := lib.get_default(parameters, "exemptions", [])
33+
2534
# Verify that resource is Cloud SQL instance and is not a first gen
2635
# Maintenance window is supported only on 2nd generation instances
2736
asset := input.asset
2837
asset.asset_type == "sqladmin.googleapis.com/Instance"
2938
asset.resource.data.backendType != "FIRST_GEN"
3039

31-
# get the maintenance window object
32-
maintenance_window := lib.get_default(asset.resource.data.settings, "maintenanceWindow", {})
40+
# Check if resource is in exempt list
41+
matches := {asset.name} & cast_set(exempt_list)
42+
count(matches) == 0
3343

34-
# non compliant when not found
35-
maintenance_window == {}
44+
# get instance settings
45+
settings := lib.get_default(asset.resource.data, "settings", {})
46+
instance_maintenance_window := lib.get_default(settings, "maintenanceWindow", {})
47+
instance_maintenance_window_hour := lib.get_default(instance_maintenance_window, "hour", "")
3648

37-
message := sprintf("%v missing maintenance window.", [asset.name])
49+
# check compliance
50+
hour_matches := {instance_maintenance_window_hour} & cast_set(desired_maintenance_window_hours)
51+
count(hour_matches) == 0
52+
53+
message := sprintf("%v missing or incorrect maintenance window. Hour: '%v'", [asset.name, instance_maintenance_window_hour])
3854
metadata := {"resource": asset.name}
3955
}
56+
57+
# Rule utilities
58+
get_default_when_empty(value, default_value) = output {
59+
count(value) != 0
60+
output := value
61+
}
62+
63+
get_default_when_empty(value, default_value) = output {
64+
count(value) == 0
65+
output := default_value
66+
}

validator/sql_maintenance_window_test.rego

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,86 @@
1616

1717
package templates.gcp.GCPSQLMaintenanceWindowConstraintV1
1818

19-
all_violations[violation] {
20-
resource := data.test.fixtures.sql_maintenance_window.assets[_]
21-
constraint := data.test.fixtures.sql_maintenance_window.constraints
22-
23-
issues := deny with input.asset as resource
19+
import data.test.fixtures.sql_maintenance_window.assets as fixture_assets
20+
import data.test.fixtures.sql_maintenance_window.constraints as fixture_constraints
2421

22+
find_violations[violation] {
23+
asset := data.test_assets[_]
24+
constraint := data.test_constraints[_]
25+
issues := deny with input.asset as asset with input.constraint as constraint
2526
violation := issues[_]
2627
}
2728

28-
# Confirm total violations count
29-
test_sql_maintenance_window_violations_count {
30-
count(all_violations) == 2
29+
# without parameters the constraint should still detect sql instances without maintenance window
30+
no_parameter[violation] {
31+
constraints := [fixture_constraints.no_parameter]
32+
found_violations := find_violations with data.test_assets as fixture_assets
33+
with data.test_constraints as constraints
34+
35+
violation := found_violations[_]
3136
}
3237

33-
test_sql_maintenance_window_violations_basic {
34-
found_violations := all_violations
38+
test_no_parameter_count {
39+
count(no_parameter) == 2
40+
}
3541

42+
test_no_parameter_list {
43+
found_violations := no_parameter
3644
found_violations[_].details.resource == "//cloudsql.googleapis.com/projects/brunore-db-test/instances/mysqlv2-nomaintenance"
3745
found_violations[_].details.resource == "//cloudsql.googleapis.com/projects/brunore-db-test/instances/postgres-nomaintenance"
3846
}
47+
48+
# with an empty hours field the constraint should detect sql insta nceswithout maintenance window
49+
no_hour[violation] {
50+
constraints := [fixture_constraints.no_hour]
51+
found_violations := find_violations with data.test_assets as fixture_assets
52+
with data.test_constraints as constraints
53+
54+
violation := found_violations[_]
55+
}
56+
57+
test_no_hour_count {
58+
count(no_hour) == 2
59+
}
60+
61+
test_no_hour_list {
62+
found_violations := no_hour
63+
found_violations[_].details.resource == "//cloudsql.googleapis.com/projects/brunore-db-test/instances/mysqlv2-nomaintenance"
64+
found_violations[_].details.resource == "//cloudsql.googleapis.com/projects/brunore-db-test/instances/postgres-nomaintenance"
65+
}
66+
67+
specific_hours[violation] {
68+
constraints := [fixture_constraints.specific_hours]
69+
found_violations := find_violations with data.test_assets as fixture_assets
70+
with data.test_constraints as constraints
71+
72+
violation := found_violations[_]
73+
}
74+
75+
test_specific_hours_count {
76+
count(specific_hours) == 3
77+
}
78+
79+
test_specific_hours_list {
80+
found_violations := specific_hours
81+
found_violations[_].details.resource == "//cloudsql.googleapis.com/projects/brunore-db-test/instances/mysqlv2-nomaintenance"
82+
found_violations[_].details.resource == "//cloudsql.googleapis.com/projects/brunore-db-test/instances/postgres-nomaintenance"
83+
found_violations[_].details.resource == "//cloudsql.googleapis.com/projects/brunore-cai-test/instances/brunore-mon-3pm"
84+
}
85+
86+
exemption[violation] {
87+
constraints := [fixture_constraints.exemption]
88+
found_violations := find_violations with data.test_assets as fixture_assets
89+
with data.test_constraints as constraints
90+
91+
violation := found_violations[_]
92+
}
93+
94+
test_exemption_count {
95+
count(exemption) == 1
96+
}
97+
98+
test_exemption_list {
99+
found_violations := exemption
100+
found_violations[_].details.resource == "//cloudsql.googleapis.com/projects/brunore-db-test/instances/postgres-nomaintenance"
101+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#
2+
# Copyright 2019 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
apiVersion: constraints.gatekeeper.sh/v1alpha1
18+
kind: GCPSQLMaintenanceWindowConstraintV1
19+
metadata:
20+
name: sql-maintenance-window-test-instance-exemptions
21+
spec:
22+
severity: high
23+
match:
24+
target: ["organization/*"]
25+
parameters:
26+
hours: []
27+
exemptions:
28+
- //cloudsql.googleapis.com/projects/brunore-db-test/instances/mysqlv2-nomaintenance
29+

validator/test/fixtures/sql_maintenance_window/constraints/data.yaml renamed to validator/test/fixtures/sql_maintenance_window/constraints/no_hour/data.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@
1717
apiVersion: constraints.gatekeeper.sh/v1alpha1
1818
kind: GCPSQLMaintenanceWindowConstraintV1
1919
metadata:
20-
name: gcp-sql-maintenance-window-v1
20+
name: sql-maintenance-window-test-empty-hour-parameter
2121
spec:
2222
severity: high
2323
match:
2424
target: ["organization/*"]
25+
parameters:
26+
hours: []
27+
exemptions: []
28+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#
2+
# Copyright 2019 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
apiVersion: constraints.gatekeeper.sh/v1alpha1
18+
kind: GCPSQLMaintenanceWindowConstraintV1
19+
metadata:
20+
name: sql-maintenance-window-test-missing-parameters-object # this ensure no breaking change with the previous version
21+
spec:
22+
severity: high
23+
match:
24+
target: ["organization/*"]
25+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#
2+
# Copyright 2019 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
apiVersion: constraints.gatekeeper.sh/v1alpha1
18+
kind: GCPSQLMaintenanceWindowConstraintV1
19+
metadata:
20+
name: sql-maintenance-window-test-authorized-maintenance-hours
21+
spec:
22+
severity: high
23+
match:
24+
target: ["organization/*"]
25+
parameters:
26+
hours: # UTC time,
27+
- 20
28+
- 21
29+
- 22
30+
- 23
31+
- 0
32+
- 1
33+
- 2
34+
- 3
35+
- 4
36+
- 5
37+
exemptions: []
38+

0 commit comments

Comments
 (0)