blob: 4e1ab70054272d5f26ad1607fa5578513772e2d9 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
# frozen_string_literal: true
module Gitlab
module Database
module QueryAnalyzers
class RestrictAllowedSchemas < Base
UnsupportedSchemaError = Class.new(QueryAnalyzerError)
DDLNotAllowedError = Class.new(UnsupportedSchemaError)
DMLNotAllowedError = Class.new(UnsupportedSchemaError)
DMLAccessDeniedError = Class.new(UnsupportedSchemaError)
# Re-map schemas observed schemas to a single cluster mode
# - symbol:
# The mapped schema indicates that it contains all data in a single-cluster mode
# - nil:
# Inidicates that changes made to this schema are ignored and always allowed
SCHEMA_MAPPING = {
gitlab_shared: nil,
gitlab_internal: nil,
# Pods specific changes
gitlab_main_clusterwide: :gitlab_main,
gitlab_main_cell: :gitlab_main
}.freeze
class << self
def enabled?
true
end
def allowed_gitlab_schemas
self.context[:allowed_gitlab_schemas]
end
def allowed_gitlab_schemas=(value)
self.context[:allowed_gitlab_schemas] = value
end
def analyze(parsed)
# If list of schemas is empty, we allow only DDL changes
if self.dml_mode?
self.restrict_to_dml_only(parsed)
else
self.restrict_to_ddl_only(parsed)
end
end
def require_ddl_mode!(message = "")
return unless self.context
self.raise_dml_not_allowed_error(message) if self.dml_mode?
end
def require_dml_mode!(message = "")
return unless self.context
self.raise_ddl_not_allowed_error(message) if self.ddl_mode?
end
private
def restrict_to_ddl_only(parsed)
tables = self.dml_tables(parsed)
schemas = self.dml_schemas(tables)
schemas = self.map_schemas(schemas)
if schemas.any?
self.raise_dml_not_allowed_error("Modifying of '#{tables}' (#{schemas.to_a}) with '#{parsed.sql}'")
end
end
def restrict_to_dml_only(parsed)
if parsed.pg.ddl_tables.any?
self.raise_ddl_not_allowed_error("Modifying of '#{parsed.pg.ddl_tables}' with '#{parsed.sql}'")
end
if parsed.pg.ddl_functions.any?
self.raise_ddl_not_allowed_error("Modifying of '#{parsed.pg.ddl_functions}' with '#{parsed.sql}'")
end
tables = self.dml_tables(parsed)
schemas = self.dml_schemas(tables)
schemas = self.map_schemas(schemas)
allowed_schemas = self.map_schemas(self.allowed_gitlab_schemas)
if (schemas - allowed_schemas).any?
raise DMLAccessDeniedError, \
"Select/DML queries (SELECT/UPDATE/DELETE) do access '#{tables}' (#{schemas.to_a}) " \
"which is outside of list of allowed schemas: '#{self.allowed_gitlab_schemas}'. " \
"#{documentation_url}"
end
end
def dml_mode?
self.allowed_gitlab_schemas&.any?
end
def ddl_mode?
!self.dml_mode?
end
def dml_tables(parsed)
parsed.pg.select_tables + parsed.pg.dml_tables
end
def dml_schemas(tables)
::Gitlab::Database::GitlabSchema.table_schemas!(tables)
end
def map_schemas(schemas)
schemas = schemas.to_set
SCHEMA_MAPPING.each do |in_schema, mapped_schema|
next unless schemas.delete?(in_schema)
schemas.add(mapped_schema) if mapped_schema
end
schemas
end
def raise_dml_not_allowed_error(message)
raise DMLNotAllowedError, \
"Select/DML queries (SELECT/UPDATE/DELETE) are disallowed in the DDL (structure) mode. " \
"#{message}. #{documentation_url}" \
end
def raise_ddl_not_allowed_error(message)
raise DDLNotAllowedError, \
"DDL queries (structure) are disallowed in the Select/DML (SELECT/UPDATE/DELETE) mode. " \
"#{message}. #{documentation_url}"
end
def documentation_url
"For more information visit: https://docs.gitlab.com/ee/development/database/migrations_for_multiple_databases.html"
end
end
end
end
end
end
|