1 /* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
2 
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6 
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation.  The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License, version 2.0, for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */
22 
23 #include "sql/sql_alter_instance.h" /* Alter_instance class */
24 
25 #include <utility>
26 
27 #include "lex_string.h"
28 #include "m_string.h"
29 #include "mutex_lock.h"
30 #include "my_dbug.h"
31 #include "my_inttypes.h"
32 #include "my_sys.h" /* my_error */
33 #include "mysqld_error.h"
34 #include "sql/auth/auth_acls.h"
35 #include "sql/auth/sql_security_ctx.h"
36 #include "sql/derror.h"  /* ER_THD */
37 #include "sql/handler.h" /* ha_resolve_by_legacy_type */
38 #include "sql/lock.h"    /* acquire_shared_global_read_lock */
39 #include "sql/mysqld.h"
40 #include "sql/rpl_log_encryption.h"
41 #include "sql/sql_backup_lock.h" /* acquire_shared_backup_lock */
42 #include "sql/sql_class.h"       /* THD */
43 #include "sql/sql_error.h"
44 #include "sql/sql_lex.h"
45 #include "sql/sql_plugin_ref.h"
46 #include "sql/sql_table.h" /* write_to_binlog */
47 
48 /*
49   @brief
50   Log current command to binlog
51 
52   @returns false on success,
53            true on error
54 
55   In case of failure, appropriate error is logged.
56 */
57 
log_to_binlog()58 bool Alter_instance::log_to_binlog() {
59   bool res = false;
60   if (!m_thd->lex->no_write_to_binlog)
61     res =
62         write_bin_log(m_thd, false, m_thd->query().str, m_thd->query().length);
63 
64   return res;
65 }
66 
67 /*
68   @brief
69   Executes master key rotation by calling SE api.
70 
71   @returns false on success
72            true on error
73 
74   In case of failure, appropriate error
75   is logged by function.
76 */
77 
execute()78 bool Rotate_innodb_master_key::execute() {
79   const LEX_CSTRING storage_engine = {STRING_WITH_LEN("innodb")};
80   plugin_ref se_plugin;
81   handlerton *hton;
82 
83   Security_context *sctx = m_thd->security_context();
84   if (!sctx->check_access(SUPER_ACL) &&
85       !sctx->has_global_grant(STRING_WITH_LEN("ENCRYPTION_KEY_ADMIN")).first) {
86     my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0),
87              "SUPER or ENCRYPTION_KEY_ADMIN");
88     return true;
89   }
90 
91   if ((se_plugin = ha_resolve_by_name(m_thd, &storage_engine, false))) {
92     hton = plugin_data<handlerton *>(se_plugin);
93   } else {
94     my_error(ER_MASTER_KEY_ROTATION_SE_UNAVAILABLE, MYF(0));
95     return true;
96   }
97 
98   if (!hton->rotate_encryption_master_key) {
99     my_error(ER_MASTER_KEY_ROTATION_NOT_SUPPORTED_BY_SE, MYF(0));
100     return true;
101   }
102 
103   /*
104     Acquire protection against GRL and check for concurrent change of read_only
105     value since encryption key rotation is not allowed in read_only/
106     super_read_only mode.
107   */
108   if (acquire_shared_global_read_lock(m_thd,
109                                       m_thd->variables.lock_wait_timeout)) {
110     // MDL subsystem has to set an error in Diagnostics Area
111     DBUG_ASSERT(m_thd->get_stmt_da()->is_error());
112     return true;
113   }
114 
115   /*
116     Acquire shared backup lock to block concurrent backup. Acquire exclusive
117     backup lock to block any concurrent DDL. The fact that we acquire both
118     these locks also ensures that concurrent KEY rotation requests are blocked.
119   */
120   if (acquire_exclusive_backup_lock(m_thd, m_thd->variables.lock_wait_timeout,
121                                     true) ||
122       acquire_shared_backup_lock(m_thd, m_thd->variables.lock_wait_timeout)) {
123     // MDL subsystem has to set an error in Diagnostics Area
124     DBUG_ASSERT(m_thd->get_stmt_da()->is_error());
125     return true;
126   }
127 
128   if (hton->rotate_encryption_master_key()) {
129     /* SE should have raised error */
130     DBUG_ASSERT(m_thd->get_stmt_da()->is_error());
131     return true;
132   }
133 
134   if (log_to_binlog()) {
135     /*
136       Though we failed to write to binlog,
137       there is no way we can undo this operation.
138       So, covert error to a warning and let user
139       know that something went wrong while trying
140       to make entry in binlog.
141     */
142     m_thd->clear_error();
143     m_thd->get_stmt_da()->reset_diagnostics_area();
144 
145     push_warning(m_thd, Sql_condition::SL_WARNING,
146                  ER_MASTER_KEY_ROTATION_BINLOG_FAILED,
147                  ER_THD(m_thd, ER_MASTER_KEY_ROTATION_BINLOG_FAILED));
148   }
149 
150   my_ok(m_thd);
151   return false;
152 }
153 
execute()154 bool Innodb_redo_log::execute() {
155   DBUG_TRACE;
156 
157   const LEX_CSTRING storage_engine = {STRING_WITH_LEN("innodb")};
158 
159   auto hton = plugin_data<handlerton *>(
160       ha_resolve_by_name(m_thd, &storage_engine, false));
161 
162   if (hton == nullptr) {
163     /* Innodb engine is not loaded. Should never happen. */
164     my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), storage_engine.str);
165   }
166 
167   Security_context *sctx = m_thd->security_context();
168   if (!sctx->has_global_grant(STRING_WITH_LEN("INNODB_REDO_LOG_ENABLE"))
169            .first) {
170     my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "INNODB_REDO_LOG_ENABLE");
171     return true;
172   }
173 
174   /*
175     Acquire shared backup lock to block concurrent backup. Acquire exclusive
176     backup lock to block any concurrent DDL. This would also serialize any
177     concurrent key rotation and other redo log enable/disable calls.
178   */
179   if (acquire_exclusive_backup_lock(m_thd, m_thd->variables.lock_wait_timeout,
180                                     true) ||
181       acquire_shared_backup_lock(m_thd, m_thd->variables.lock_wait_timeout)) {
182     DBUG_ASSERT(m_thd->get_stmt_da()->is_error());
183     return true;
184   }
185 
186   if (hton->redo_log_set_state(m_thd, m_enable)) {
187     /* SE should have raised error */
188     DBUG_ASSERT(m_thd->get_stmt_da()->is_error());
189     return true;
190   }
191 
192   /* Right now, we don't log this command to binary log as redo logging
193   options are low level physical attribute which is not needed to replicate
194   to other instances. */
195 
196   my_ok(m_thd);
197   return false;
198 }
199 
execute()200 bool Rotate_binlog_master_key::execute() {
201   DBUG_TRACE;
202 
203   MUTEX_LOCK(lock, &LOCK_rotate_binlog_master_key);
204 
205   Security_context *sctx = m_thd->security_context();
206   if (!sctx->check_access(SUPER_ACL) &&
207       !sctx->has_global_grant(STRING_WITH_LEN("BINLOG_ENCRYPTION_ADMIN"))
208            .first) {
209     my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0),
210              "SUPER or BINLOG_ENCRYPTION_ADMIN");
211     return true;
212   }
213 
214   if (!rpl_encryption.is_enabled()) {
215     my_error(ER_RPL_ENCRYPTION_CANNOT_ROTATE_BINLOG_MASTER_KEY, MYF(0));
216     return true;
217   }
218 
219   if (rpl_encryption.remove_remaining_seqnos_from_keyring()) return true;
220 
221   if (rpl_encryption.rotate_master_key()) return true;
222 
223   my_ok(m_thd);
224   return false;
225 }
226