-
Notifications
You must be signed in to change notification settings - Fork 11
Read-only Toggle #60
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
base: main
Are you sure you want to change the base?
Read-only Toggle #60
Changes from all commits
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 |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import ( | |
| "time" | ||
|
|
||
| "github.com/Infisical/infisical-merge/packages/pam/session" | ||
| sqi "github.com/Infisical/sql-query-identifier" | ||
| "github.com/go-mysql-org/go-mysql/client" | ||
| "github.com/go-mysql-org/go-mysql/mysql" | ||
| "github.com/pkg/errors" | ||
|
|
@@ -16,10 +17,11 @@ type RelayHandler struct { | |
| selfServerConn *client.Conn | ||
| sessionLogger session.SessionLogger | ||
| closed atomic.Bool | ||
| config MysqlProxyConfig | ||
| } | ||
|
|
||
| func NewRelayHandler(selfServerConn *client.Conn, sessionLogger session.SessionLogger) *RelayHandler { | ||
| return &RelayHandler{selfServerConn, sessionLogger, atomic.Bool{}} | ||
| func NewRelayHandler(selfServerConn *client.Conn, sessionLogger session.SessionLogger, config MysqlProxyConfig) *RelayHandler { | ||
| return &RelayHandler{selfServerConn, sessionLogger, atomic.Bool{}, config} | ||
| } | ||
|
|
||
| func (r *RelayHandler) Closed() bool { | ||
|
|
@@ -33,6 +35,15 @@ func (r *RelayHandler) UseDB(dbName string) error { | |
| } | ||
|
|
||
| func (r *RelayHandler) HandleQuery(query string) (*mysql.Result, error) { | ||
| if r.config.ReadOnlyMode { | ||
| isReadOnly, err := r.handleReadOnlyCheck(query) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if !isReadOnly { | ||
| return nil, mysql.NewError(mysql.ER_OPTION_PREVENTS_STATEMENT, "Operation not allowed by policy in read-only mode.") | ||
| } | ||
| } | ||
|
Comment on lines
+38
to
+46
Contributor
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. Seems like repeating, extracted into some thing like err = checkReadOnly(...)
if err != nil {
return ...
}instead? |
||
| result, err := r.selfServerConn.Execute(query) | ||
| r.checkConnLostError(err) | ||
| if err != nil { | ||
|
|
@@ -59,6 +70,15 @@ func (r *RelayHandler) HandleFieldList(table string, fieldWildcard string) ([]*m | |
| } | ||
|
|
||
| func (r *RelayHandler) HandleStmtPrepare(query string) (params int, columns int, context interface{}, err error) { | ||
| if r.config.ReadOnlyMode { | ||
| isReadOnly, err := r.handleReadOnlyCheck(query) | ||
| if err != nil { | ||
| return 0, 0, nil, err | ||
| } | ||
| if !isReadOnly { | ||
| return 0, 0, nil, mysql.NewError(mysql.ER_OPTION_PREVENTS_STATEMENT, "Operation not allowed by policy in read-only mode.") | ||
| } | ||
| } | ||
|
Comment on lines
+73
to
+81
Contributor
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. DRY |
||
| stmt, err := r.selfServerConn.Prepare(query) | ||
| r.checkConnLostError(err) | ||
| if err != nil { | ||
|
|
@@ -68,6 +88,15 @@ func (r *RelayHandler) HandleStmtPrepare(query string) (params int, columns int, | |
| } | ||
|
|
||
| func (r *RelayHandler) HandleStmtExecute(context interface{}, query string, args []interface{}) (*mysql.Result, error) { | ||
| if r.config.ReadOnlyMode { | ||
| isReadOnly, err := r.handleReadOnlyCheck(query) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if !isReadOnly { | ||
| return nil, mysql.NewError(mysql.ER_OPTION_PREVENTS_STATEMENT, "Operation not allowed by policy in read-only mode.") | ||
| } | ||
| } | ||
|
Comment on lines
+91
to
+99
Contributor
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. DRY |
||
| stmt := context.(*client.Stmt) | ||
| result, err := stmt.Execute(args...) | ||
| r.checkConnLostError(err) | ||
|
|
@@ -114,6 +143,43 @@ func (r *RelayHandler) writeLogEntry(entry session.SessionLogEntry) (*mysql.Resu | |
| return nil, nil | ||
| } | ||
|
|
||
| func (r *RelayHandler) handleReadOnlyCheck(query string) (bool, error) { | ||
| if query == "" { | ||
| return true, nil | ||
| } | ||
|
|
||
| dialect := sqi.DialectMySQL | ||
| strict := false | ||
| options := sqi.IdentifyOptions{ | ||
| Dialect: &dialect, | ||
| Strict: &strict, | ||
| } | ||
|
|
||
| identifiedQueries, err := sqi.Identify(query, options) | ||
| if err != nil { | ||
| log.Error(). | ||
| Str("sessionID", r.config.SessionID). | ||
| Str("query", query). | ||
| Err(err). | ||
| Msg("Failed to identify query; blocking in read-only mode.") | ||
| return false, err | ||
| } | ||
x032205 marked this conversation as resolved.
Show resolved
Hide resolved
x032205 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // verify that every statement in the query is read-only | ||
| for _, identifiedQuery := range identifiedQueries { | ||
| if identifiedQuery.ExecutionType != sqi.ExecutionListing && identifiedQuery.ExecutionType != sqi.ExecutionInformation { | ||
| log.Warn(). | ||
| Str("sessionID", r.config.SessionID). | ||
| Str("query", query). | ||
| Str("executionType", string(identifiedQuery.ExecutionType)). | ||
|
Comment on lines
+151
to
+174
Contributor
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. There should be automatic unit test tests covering different SQL statements trying to see if it can parse them correctly and detect write syntax. One test case for mysql and another for postgresql. |
||
| Msg("Write query blocked in read-only mode.") | ||
| return false, nil | ||
| } | ||
| } | ||
|
|
||
| return true, nil | ||
| } | ||
|
|
||
| func formatResult(result *mysql.Result) string { | ||
| if result.Resultset != nil { | ||
| return fmt.Sprintf("SUCCESS (%d rows affected)", len(result.Resultset.Values)) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.